EHS-Studio Claude Sonnet 5.5 commited on
Commit
9da4a9b
·
1 Parent(s): 3ce72c8

v0.16.3: native DSSL spec, and the seven bugs writing it found

Browse files

native/ holds a written specification of DSSL (12 chapters, two generated appendices), the shared
vocabulary data, and a Rust crate stub. No browser implements DSSL natively; this is the spec a
browser team would implement from, checked against the engine by scripts/test-native-spec.mjs.

Writing it showed where the engine and the language disagreed. Fixed:
- an unquoted url(http://...) lost the rest of the line to the comment stripper
- a typo at the end of a token path silently resolved to its parent token
- nesting under a selector list joined the wrong way (.a, .b { .c {} } and hover {})
- a backslash did not escape a quote in a value
- a nested li (or any HTML element name) got a "did you mean link" warning
New warnings, both compile fine: an unknown @layer name, and radius: xl with no token.

Every .dssl file in the repo and every doc example resolve to identical output before and after.
Open: D-6 (registry rule across files) needs a browser test. Binaries follow in a separate commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

GUIDE.md CHANGED
@@ -950,7 +950,7 @@ and listed, so the file you get always compiles.
950
  ```js
951
  import { DSSL } from '/dssl/index.js' // or just use window.DSSL after boot
952
 
953
- DSSL.version // "0.16.2": a string, not a function
954
  DSSL.compile(source) // { ok, errors, warnings, stats, doc }: parse and resolve, no DOM
955
  DSSL.apply(source, { name, scope, replace })
956
  await DSSL.load(['a.dssl', 'b.dssl'])
@@ -985,7 +985,7 @@ Three ways to get it:
985
 
986
  1. VSCodium: open the Extensions tab, search "DSSL" and install. It is on Open VSX:
987
  https://open-vsx.org/extension/ehs-studio/dssl-vscode
988
- 2. VS Code: download `dssl-vscode/dssl-vscode-0.16.2.vsix` from the repo files, then
989
  Extensions panel, `...`, Install from VSIX. (The Microsoft Marketplace listing is not published yet.)
990
  3. Build it yourself: `cd dssl-vscode && npx @vscode/vsce package --no-dependencies`.
991
 
 
950
  ```js
951
  import { DSSL } from '/dssl/index.js' // or just use window.DSSL after boot
952
 
953
+ DSSL.version // "0.16.3": a string, not a function
954
  DSSL.compile(source) // { ok, errors, warnings, stats, doc }: parse and resolve, no DOM
955
  DSSL.apply(source, { name, scope, replace })
956
  await DSSL.load(['a.dssl', 'b.dssl'])
 
985
 
986
  1. VSCodium: open the Extensions tab, search "DSSL" and install. It is on Open VSX:
987
  https://open-vsx.org/extension/ehs-studio/dssl-vscode
988
+ 2. VS Code: download `dssl-vscode/dssl-vscode-0.16.3.vsix` from the repo files, then
989
  Extensions panel, `...`, Install from VSIX. (The Microsoft Marketplace listing is not published yet.)
990
  3. Build it yourself: `cd dssl-vscode && npx @vscode/vsce package --no-dependencies`.
991
 
PARITY.md CHANGED
@@ -62,6 +62,10 @@ so the boundary is explicit, not folklore.
62
  - loud errors with did-you-mean, and a warnings channel for cascade-order footguns
63
  - `freeze()` / `unfreeze()`: a live engine that can be parked at zero cost
64
 
 
 
 
 
65
  ## v0.16.2
66
 
67
  `DSSL.lint`'s `css-loses` skips the output of `dssl build` (recognised by its `@layer tokens, base, components, utilities, pin` statement), because its reset is built to sit under the engine. The docs page of this site went from 4,314 false findings (0.16.0), to 111 (0.16.1), to none from its own CSS.
 
62
  - loud errors with did-you-mean, and a warnings channel for cascade-order footguns
63
  - `freeze()` / `unfreeze()`: a live engine that can be parked at zero cost
64
 
65
+ ## v0.16.3
66
+
67
+ Seven places where the engine disagreed with the language are fixed: unquoted `url(http://...)`, token paths with a typo at the end, nesting under a selector list, an escaped quote in a value, and the false "did you mean link" warning on `li`. An unknown `@layer` name and `radius: xl` with no token now warn. All of them were found by writing a specification for native, JavaScript-free DSSL (`native/spec/`). A browser that implements DSSL natively does not exist yet: this is a written spec and checks, not a browser feature.
68
+
69
  ## v0.16.2
70
 
71
  `DSSL.lint`'s `css-loses` skips the output of `dssl build` (recognised by its `@layer tokens, base, components, utilities, pin` statement), because its reset is built to sit under the engine. The docs page of this site went from 4,314 false findings (0.16.0), to 111 (0.16.1), to none from its own CSS.
README.md CHANGED
@@ -68,7 +68,7 @@ Both drop a `dssl-engine/` folder next to you: the engine, a starter token set,
68
 
69
  Prefer to click: [dssl-engine.zip](https://dssl-sable.vercel.app/dssl-engine.zip) or [dssl-engine.tar.gz](https://dssl-sable.vercel.app/dssl-engine.tar.gz). Same package, straight from the browser.
70
 
71
- Writing your own .dssl? The official editor extension (`ehs-studio.dssl-vscode`) puts the real compiler in your editor: the squiggles are the same errors the browser console prints, completions come from your own token registry, and hovers explain the semantic vocabulary. One package for VS Code and VSCodium (same extension API). In VSCodium, search "DSSL" in the Extensions tab (it is on Open VSX: https://open-vsx.org/extension/ehs-studio/dssl-vscode). In VS Code, download `dssl-vscode/dssl-vscode-0.16.2.vsix` from the repo files, then Extensions panel -> ... -> Install from VSIX. See `dssl-vscode/PUBLISHING.md` for publishing.
72
 
73
  AI agents and machine reviewers: this repo ships `llms.txt` (dense, structured facts), `AGENTS.md` (how to work on the codebase) and `PARITY.md` (the honest feature matrix against CSS). The site serves the same `llms.txt` at https://dssl-sable.vercel.app/llms.txt
74
 
@@ -470,7 +470,7 @@ await DSSL.lint(root) // contrast against the real background,
470
  DSSL.play(el, 'boing', opts) // a one-shot motion from JS, as a promise
471
  DSSL.motion('full') // run every motion whatever the system asks for
472
  DSSL.auraZ(20) // where the aura canvas sits in the stack
473
- DSSL.version // "0.16.2", a string
474
  ```
475
 
476
  Auto-boot picks up every `link[rel="stylesheet/dssl"]` and `style[type="text/dssl"]`, applies them, and fires `dssl-ready` on `window`. `<html data-dssl-reset>` enables the small built-in reset.
@@ -546,7 +546,7 @@ const rep = DSSL.explain(document.querySelector('.doc-card'))
546
 
547
  The matcher also pre-filters every rule through a cheap class/tag/id gate extracted from the selector's rightmost compound, so a recompute skips obvious non-matches instead of asking the browser rule by rule. The gate only ever says no; anything it does not fully understand falls back to native matching.
548
 
549
- ## What works today (v0.16.2)
550
 
551
  - the full selector surface, including `:nth-child`, `:not`, `:is`, `:where`, `:has`
552
  - flexbox and grid with `row:` / `column:` / `grid:` / `stack:` / `center:` sugar, place words now own an axis (`between` is main-axis, `stretch` is cross-axis) and conflicts warn instead of silently overwriting
@@ -586,7 +586,7 @@ The matcher also pre-filters every rule through a cheap class/tag/id gate extrac
586
 
587
  ## Status
588
 
589
- v0.16.2, experimental, moving fast. The language grows whenever the site needs something it cannot express yet, so the site doubles as the test suite, and the docs examples are rendered and asserted in a real browser, so the guide cannot lie. Honest ledger: great for creative sites, prototypes, docs, playgrounds and internal tools; when a page must work without JavaScript, build the static subset with `dssl build` or pick another tool for that page. Freeze makes big static pages viable; live interaction is what the engine is for.
590
 
591
  ## License
592
 
 
68
 
69
  Prefer to click: [dssl-engine.zip](https://dssl-sable.vercel.app/dssl-engine.zip) or [dssl-engine.tar.gz](https://dssl-sable.vercel.app/dssl-engine.tar.gz). Same package, straight from the browser.
70
 
71
+ Writing your own .dssl? The official editor extension (`ehs-studio.dssl-vscode`) puts the real compiler in your editor: the squiggles are the same errors the browser console prints, completions come from your own token registry, and hovers explain the semantic vocabulary. One package for VS Code and VSCodium (same extension API). In VSCodium, search "DSSL" in the Extensions tab (it is on Open VSX: https://open-vsx.org/extension/ehs-studio/dssl-vscode). In VS Code, download `dssl-vscode/dssl-vscode-0.16.3.vsix` from the repo files, then Extensions panel -> ... -> Install from VSIX. See `dssl-vscode/PUBLISHING.md` for publishing.
72
 
73
  AI agents and machine reviewers: this repo ships `llms.txt` (dense, structured facts), `AGENTS.md` (how to work on the codebase) and `PARITY.md` (the honest feature matrix against CSS). The site serves the same `llms.txt` at https://dssl-sable.vercel.app/llms.txt
74
 
 
470
  DSSL.play(el, 'boing', opts) // a one-shot motion from JS, as a promise
471
  DSSL.motion('full') // run every motion whatever the system asks for
472
  DSSL.auraZ(20) // where the aura canvas sits in the stack
473
+ DSSL.version // "0.16.3", a string
474
  ```
475
 
476
  Auto-boot picks up every `link[rel="stylesheet/dssl"]` and `style[type="text/dssl"]`, applies them, and fires `dssl-ready` on `window`. `<html data-dssl-reset>` enables the small built-in reset.
 
546
 
547
  The matcher also pre-filters every rule through a cheap class/tag/id gate extracted from the selector's rightmost compound, so a recompute skips obvious non-matches instead of asking the browser rule by rule. The gate only ever says no; anything it does not fully understand falls back to native matching.
548
 
549
+ ## What works today (v0.16.3)
550
 
551
  - the full selector surface, including `:nth-child`, `:not`, `:is`, `:where`, `:has`
552
  - flexbox and grid with `row:` / `column:` / `grid:` / `stack:` / `center:` sugar, place words now own an axis (`between` is main-axis, `stretch` is cross-axis) and conflicts warn instead of silently overwriting
 
586
 
587
  ## Status
588
 
589
+ v0.16.3, experimental, moving fast. The language grows whenever the site needs something it cannot express yet, so the site doubles as the test suite, and the docs examples are rendered and asserted in a real browser, so the guide cannot lie. Honest ledger: great for creative sites, prototypes, docs, playgrounds and internal tools; when a page must work without JavaScript, build the static subset with `dssl build` or pick another tool for that page. Freeze makes big static pages viable; live interaction is what the engine is for.
590
 
591
  ## License
592
 
dssl-vscode/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
  # Changelog
2
 
 
 
 
 
3
  ## 0.16.2
4
 
5
  - Bundled engine 0.16.2: `DSSL.lint` also skips the output of `dssl build` (its reset sits under the engine on purpose).
 
1
  # Changelog
2
 
3
+ ## 0.16.3
4
+
5
+ - Bundled engine 0.16.3: seven bug fixes found while writing the native spec (unquoted `url(http://...)`, nesting under selector lists, token path typos, escaped quotes, a false `li` warning) and two new warnings (unknown `@layer` name, `radius: xl` without a token).
6
+
7
  ## 0.16.2
8
 
9
  - Bundled engine 0.16.2: `DSSL.lint` also skips the output of `dssl build` (its reset sits under the engine on purpose).
dssl-vscode/lib/engine/parser.js CHANGED
@@ -111,6 +111,17 @@ function stripComments(src) {
111
  continue;
112
  }
113
  if (c === '"' || c === "'") { q = c; out += c; continue; }
 
 
 
 
 
 
 
 
 
 
 
114
  if (c === '/' && n === '/') {
115
  // v0.7.1 bugfix: this handler used to emit its own '\n' AND rewind so
116
  // the main loop emitted the same newline again - every // comment
 
111
  continue;
112
  }
113
  if (c === '"' || c === "'") { q = c; out += c; continue; }
114
+ // An unquoted url(...) is one token: the // in http://x.com/a.png is not a comment. Copy it through the
115
+ // closing paren untouched (newlines included, so line numbers hold). A quoted url is handled by the string rule above.
116
+ if ((c === 'u' || c === 'U') && /^url\(/i.test(src.slice(i, i + 4)) && !/[\w-]/.test(src[i - 1] || '')) {
117
+ let k = i + 4;
118
+ while (k < src.length && /\s/.test(src[k])) k++;
119
+ if (src[k] !== '"' && src[k] !== "'") {
120
+ let e = k;
121
+ while (e < src.length && src[e] !== ')') e += src[e] === '\\' ? 2 : 1;
122
+ if (e < src.length) { out += src.slice(i, e + 1); i = e; continue; }
123
+ }
124
+ }
125
  if (c === '/' && n === '/') {
126
  // v0.7.1 bugfix: this handler used to emit its own '\n' AND rewind so
127
  // the main loop emitted the same newline again - every // comment
dssl-vscode/lib/engine/resolver.js CHANGED
@@ -182,6 +182,13 @@ const LENGTH_FN = new Set('calc,min,max,clamp,round,mod,rem,abs,sign,hypot,pow,s
182
  * - token references become var(--dssl-…), `token/NN` becomes color-mix
183
  * - unknown identifiers are errors (with did-you-mean)
184
  */
 
 
 
 
 
 
 
185
  /** split a selector list at top-level commas (not inside :is(...) and friends) */
186
  function splitTopLevel(sel) {
187
  const out = []; let depth = 0, cur = '';
@@ -209,7 +216,7 @@ function resolveValue(raw, ctx) {
209
 
210
  if (c === '"' || c === "'") {
211
  let j = i + 1;
212
- while (j < n && raw[j] !== c) j++;
213
  out += raw.slice(i, j + 1);
214
  i = j + 1;
215
  continue;
@@ -247,7 +254,7 @@ function resolveValue(raw, ctx) {
247
  if (raw[j] === ')') depth--;
248
  if (depth > 0 && (raw[j] === '"' || raw[j] === "'")) {
249
  const q = raw[j]; j++;
250
- while (j < n && raw[j] !== q) j++;
251
  }
252
  j++;
253
  }
@@ -386,16 +393,8 @@ function topSplit(str, sep) {
386
  }
387
 
388
  function findToken(tokens, ident) {
389
- if (tokens.has(ident)) return ident;
390
- // try progressive dot paths (a.b.c defined, user typed a.b.c - exact only, plus flat fallback)
391
- if (ident.includes('.')) {
392
- const parts = ident.split('.');
393
- for (let k = parts.length - 1; k > 0; k--) {
394
- const key = parts.slice(0, k).join('.');
395
- if (tokens.has(key)) return key; // hmm - partial match would change meaning; require exact
396
- }
397
- }
398
- return null;
399
  }
400
 
401
  /* ------------------------------------------------------------------ *
@@ -438,7 +437,7 @@ function splitLayers(s) {
438
  const out = []; let depth = 0, q = null, cur = '';
439
  for (let i = 0; i < s.length; i++) {
440
  const c = s[i];
441
- if (q) { cur += c; if (c === q) q = null; continue; }
442
  if (c === '"' || c === "'") { q = c; cur += c; continue; }
443
  if (c === '(' || c === '[') depth++;
444
  else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
@@ -866,7 +865,12 @@ const SEMANTIC = {
866
  radius(v, ctx) {
867
  const atoms = v.trim().split(/\s+/).filter(Boolean);
868
  const out = atoms.map(a => {
869
- if (['sm', 'md', 'lg', 'full', 'xl'].includes(a)) return `var(--dssl-radius-${a})`;
 
 
 
 
 
870
  if (/^[A-Za-z][\w-]*$/.test(a) && ctx.tokens && ctx.tokens.has('radius.' + a)) return `var(--dssl-radius-${a})`; // 0.14.0: radius { pill: 99 } then radius: pill
871
  return resolveValue(a, ctx);
872
  });
@@ -1614,7 +1618,7 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1614
  break;
1615
  }
1616
  case 'selector': {
1617
- if (/^[a-zA-Z][a-zA-Z0-9-]*$/.test(node.sel) && ctx.sel) {
1618
  const near = didYouMean(node.sel.toLowerCase(), [...STATE_WORDS]);
1619
  if (near) warnings.push({ file, line: node.line, message: `"${node.sel}" compiled as a descendant selector ("${combine(ctx.sel, node.sel)}")`, hint: `if you meant the state, it is spelled "${near}" - this block currently styles descendants called <${node.sel}>` });
1620
  }
@@ -1632,7 +1636,7 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1632
  break;
1633
  }
1634
  flushPending(node.line);
1635
- const childCtx = { ...ctx, sel: ctx.sel + ':' + node.name };
1636
  // v0.7.2 - the arrow is motion/gradient-stop syntax; inside a seen
1637
  // block it compiled into an inline style the browser rejects, so the
1638
  // element just sat at its base style forever. Say so, loudly, and
@@ -1678,7 +1682,11 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1678
  }
1679
  case 'layer':
1680
  case 'pin': {
1681
- const layer = node.kind === 'pin' ? 'pin' : (['tokens', 'base', 'components', 'utilities', 'pin'].includes(node.name) ? node.name : 'components');
 
 
 
 
1682
  flushPending(node.line);
1683
  const childCtx = { ...ctx, layer };
1684
  const sub = { decls: [] };
@@ -1688,6 +1696,7 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1688
  }
1689
  case 'pseudo': {
1690
  const base = ctx.sel || '*'; // top-level ::selection etc. means every element
 
1691
  const srule = {
1692
  id: ruleId++, sel: base, pseudo: node.name,
1693
  media: ctx.media, signals: ctx.signals, containers: ctx.containers,
@@ -1724,6 +1733,17 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1724
  fill(srule, node.children);
1725
  if (srule.decls.length) shadowRules.push(srule);
1726
  for (const st of stateRules) if (st.decls.length) shadowRules.push(st);
 
 
 
 
 
 
 
 
 
 
 
1727
  break;
1728
  }
1729
  case 'tokens': {
@@ -1994,10 +2014,24 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1994
  return { rules, shadowRules, raws, fontLinks, motions, motionFrames, errors, warnings, mediaQueries: [...mediaQueries], tokens: Object.fromEntries(tokenMap), registryRule };
1995
  }
1996
 
1997
- /** naive `&` substitution + descendant join */
 
1998
  function combine(parent, child) {
1999
- if (child.includes('&')) return child.split('&').join(parent);
2000
- return parent + ' ' + child;
 
 
 
 
 
 
 
 
 
 
 
 
 
2001
  }
2002
 
2003
  /* v0.5.0 - where does a selector carry a pseudo-element?
 
182
  * - token references become var(--dssl-…), `token/NN` becomes color-mix
183
  * - unknown identifiers are errors (with did-you-mean)
184
  */
185
+ /** HTML element names: a bare one nested in a rule is a descendant selector on purpose, never a mistyped state word */
186
+ const HTML_ELEMENTS = new Set(('a,abbr,acronym,address,area,article,aside,audio,b,base,bdi,bdo,big,blockquote,body,br,button,canvas,caption,center,cite,code,col,colgroup,' +
187
+ 'data,datalist,dd,del,details,dfn,dialog,dir,div,dl,dt,em,embed,fieldset,figcaption,figure,font,footer,form,frame,frameset,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,' +
188
+ 'i,iframe,img,input,ins,kbd,label,legend,li,link,main,map,mark,marquee,menu,meta,meter,nav,nobr,noscript,object,ol,optgroup,option,output,p,param,picture,pre,progress,q,' +
189
+ 'rp,rt,ruby,s,samp,script,search,section,select,slot,small,source,span,strike,strong,style,sub,summary,sup,table,tbody,td,template,textarea,tfoot,th,thead,time,title,tr,' +
190
+ 'track,tt,u,ul,var,video,wbr,svg,math').split(','));
191
+
192
  /** split a selector list at top-level commas (not inside :is(...) and friends) */
193
  function splitTopLevel(sel) {
194
  const out = []; let depth = 0, cur = '';
 
216
 
217
  if (c === '"' || c === "'") {
218
  let j = i + 1;
219
+ while (j < n && raw[j] !== c) j += raw[j] === '\\' ? 2 : 1; // a backslash escapes the next character
220
  out += raw.slice(i, j + 1);
221
  i = j + 1;
222
  continue;
 
254
  if (raw[j] === ')') depth--;
255
  if (depth > 0 && (raw[j] === '"' || raw[j] === "'")) {
256
  const q = raw[j]; j++;
257
+ while (j < n && raw[j] !== q) j += raw[j] === '\\' ? 2 : 1;
258
  }
259
  j++;
260
  }
 
393
  }
394
 
395
  function findToken(tokens, ident) {
396
+ // exact key only: color.ink2.bogus must not quietly become color.ink2, it falls through to "unknown value"
397
+ return tokens.has(ident) ? ident : null;
 
 
 
 
 
 
 
 
398
  }
399
 
400
  /* ------------------------------------------------------------------ *
 
437
  const out = []; let depth = 0, q = null, cur = '';
438
  for (let i = 0; i < s.length; i++) {
439
  const c = s[i];
440
+ if (q) { cur += c; if (c === q && s[i - 1] !== '\\') q = null; continue; }
441
  if (c === '"' || c === "'") { q = c; cur += c; continue; }
442
  if (c === '(' || c === '[') depth++;
443
  else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
 
865
  radius(v, ctx) {
866
  const atoms = v.trim().split(/\s+/).filter(Boolean);
867
  const out = atoms.map(a => {
868
+ if (['sm', 'md', 'lg', 'full', 'xl'].includes(a)) {
869
+ if (ctx.tokens && !ctx.tokens.has('radius.' + a) && ctx.warnings) {
870
+ ctx.warnings.push({ file: ctx.file, line: ctx.line, message: `radius "${a}" is not defined, so it will not apply`, hint: `add it with tokens { radius { ${a}: 32 } }` });
871
+ }
872
+ return `var(--dssl-radius-${a})`;
873
+ }
874
  if (/^[A-Za-z][\w-]*$/.test(a) && ctx.tokens && ctx.tokens.has('radius.' + a)) return `var(--dssl-radius-${a})`; // 0.14.0: radius { pill: 99 } then radius: pill
875
  return resolveValue(a, ctx);
876
  });
 
1618
  break;
1619
  }
1620
  case 'selector': {
1621
+ if (/^[a-zA-Z][a-zA-Z0-9-]*$/.test(node.sel) && ctx.sel && !HTML_ELEMENTS.has(node.sel.toLowerCase())) {
1622
  const near = didYouMean(node.sel.toLowerCase(), [...STATE_WORDS]);
1623
  if (near) warnings.push({ file, line: node.line, message: `"${node.sel}" compiled as a descendant selector ("${combine(ctx.sel, node.sel)}")`, hint: `if you meant the state, it is spelled "${near}" - this block currently styles descendants called <${node.sel}>` });
1624
  }
 
1636
  break;
1637
  }
1638
  flushPending(node.line);
1639
+ const childCtx = { ...ctx, sel: withState(ctx.sel, node.name) };
1640
  // v0.7.2 - the arrow is motion/gradient-stop syntax; inside a seen
1641
  // block it compiled into an inline style the browser rejects, so the
1642
  // element just sat at its base style forever. Say so, loudly, and
 
1682
  }
1683
  case 'layer':
1684
  case 'pin': {
1685
+ const LAYERS = ['tokens', 'base', 'components', 'utilities', 'pin'];
1686
+ const layer = node.kind === 'pin' ? 'pin' : (LAYERS.includes(node.name) ? node.name : 'components');
1687
+ if (node.kind === 'layer' && !LAYERS.includes(node.name)) {
1688
+ warnings.push({ file, line: node.line, message: `unknown layer "${node.name}", treated as "components"`, hint: didYouMean(node.name, LAYERS) || 'the layers are tokens, base, components, utilities, pin' });
1689
+ }
1690
  flushPending(node.line);
1691
  const childCtx = { ...ctx, layer };
1692
  const sub = { decls: [] };
 
1696
  }
1697
  case 'pseudo': {
1698
  const base = ctx.sel || '*'; // top-level ::selection etc. means every element
1699
+ const parts = splitTopLevel(base);
1700
  const srule = {
1701
  id: ruleId++, sel: base, pseudo: node.name,
1702
  media: ctx.media, signals: ctx.signals, containers: ctx.containers,
 
1733
  fill(srule, node.children);
1734
  if (srule.decls.length) shadowRules.push(srule);
1735
  for (const st of stateRules) if (st.decls.length) shadowRules.push(st);
1736
+ // a selector list in front of the block (.a, .b { ::before { } }): the rules above were built on the whole
1737
+ // list; give every further part its own copy so each gets its own pseudo-element rule
1738
+ if (parts.length > 1) {
1739
+ const built = [srule, ...stateRules].filter((r) => r.decls.length);
1740
+ for (const part of parts.slice(1)) {
1741
+ for (const r of built) {
1742
+ shadowRules.push({ ...r, decls: r.decls.map((d) => ({ ...d })), id: ruleId++, order: order++, sel: part + r.sel.slice(base.length) });
1743
+ }
1744
+ }
1745
+ for (const r of built) r.sel = parts[0] + r.sel.slice(base.length);
1746
+ }
1747
  break;
1748
  }
1749
  case 'tokens': {
 
2014
  return { rules, shadowRules, raws, fontLinks, motions, motionFrames, errors, warnings, mediaQueries: [...mediaQueries], tokens: Object.fromEntries(tokenMap), registryRule };
2015
  }
2016
 
2017
+ /** `&` substitution + descendant join. Lists are split at top-level commas and every parent part is joined with
2018
+ * every child part (parent-major), so `.a, .b { .c { } }` is `.a .c, .b .c`. Commas inside :is( ) or [ ] stay put. */
2019
  function combine(parent, child) {
2020
+ const pp = splitTopLevel(parent), cp = splitTopLevel(child);
2021
+ if (pp.length <= 1 && cp.length <= 1) {
2022
+ if (child.includes('&')) return child.split('&').join(parent);
2023
+ return parent + ' ' + child;
2024
+ }
2025
+ const out = [];
2026
+ for (const p of pp) for (const c of cp) out.push(c.includes('&') ? c.split('&').join(p) : p + ' ' + c);
2027
+ return out.join(', ');
2028
+ }
2029
+
2030
+ /** append a state to every part of a selector list: `.a, .b` + hover is `.a:hover, .b:hover` */
2031
+ function withState(sel, name) {
2032
+ const parts = splitTopLevel(sel);
2033
+ if (parts.length <= 1) return sel + ':' + name;
2034
+ return parts.map((p) => p + ':' + name).join(', ');
2035
  }
2036
 
2037
  /* v0.5.0 - where does a selector carry a pseudo-element?
dssl-vscode/package.json CHANGED
@@ -2,7 +2,7 @@
2
  "name": "dssl-vscode",
3
  "displayName": "DSSL Language Support",
4
  "description": "The DSSL style language in your editor: syntax highlighting, real-compiler diagnostics (the same errors the browser console shows), completions from the live token registry and for textures, auras and sliders, hover docs, an interactive designer with sliders and live previews for aura, texture and range sliders, and a CSS to DSSL converter. Works in VS Code and VSCodium.",
5
- "version": "0.16.2",
6
  "publisher": "ehs-studio",
7
  "icon": "icon.png",
8
  "license": "MIT",
 
2
  "name": "dssl-vscode",
3
  "displayName": "DSSL Language Support",
4
  "description": "The DSSL style language in your editor: syntax highlighting, real-compiler diagnostics (the same errors the browser console shows), completions from the live token registry and for textures, auras and sliders, hover docs, an interactive designer with sliders and live previews for aura, texture and range sliders, and a CSS to DSSL converter. Works in VS Code and VSCodium.",
5
+ "version": "0.16.3",
6
  "publisher": "ehs-studio",
7
  "icon": "icon.png",
8
  "license": "MIT",
llms.txt CHANGED
@@ -5,7 +5,7 @@
5
  - Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
6
  - Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
7
  - Install: `curl -fsSL https://dssl-sable.vercel.app/install.sh | sh` (or the zip: https://dssl-sable.vercel.app/dssl-engine.zip)
8
- - Version: 0.16.2 (engine), MIT license
9
  - Feature matrix vs CSS: see PARITY.md in the repo
10
  - For agents working ON the codebase: see AGENTS.md in the repo
11
 
@@ -64,6 +64,10 @@ DSSL.replay(sel) / DSSL.refresh([el]) / DSSL.errors() (a function) / DSSL.versio
64
  - Alpha on a named colour works: `white/12`, `currentColor/20`. It used to compile to `white/12px`.
65
  - The playground has a canvas.html tab, `?example=` links, lint warnings and an "as CSS" tab. The canvas keeps a short allow-list of tags and drops scripts, handlers, styles and ids.
66
 
 
 
 
 
67
  ## 0.16.2
68
  - `DSSL.lint`'s `css-loses` no longer judges the output of `dssl build` (its reset is built to sit under the engine), and ignores a rule whose value is the same as what DSSL wrote inline. On this site's docs page it reported 4,314 findings in 0.16.0; it now reports none from the site's own CSS.
69
 
@@ -132,4 +136,4 @@ Wait for the `dssl-ready` event on `window` before measuring layout: the engine
132
 
133
  ## Editor support
134
 
135
- The official extension (VS Code and VSCodium, same .vsix) bundles the real parser and resolver: diagnostics identical to the browser console, completions from the workspace token registry, hover docs for the semantic vocabulary. In VSCodium, search "DSSL" in the Extensions tab (it is on Open VSX: https://open-vsx.org/extension/ehs-studio/dssl-vscode). In VS Code, download `dssl-vscode/dssl-vscode-0.16.2.vsix` from the repo files, then Extensions panel -> ... -> Install from VSIX. Source lives in `dssl-vscode/` in the repo.
 
5
  - Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
6
  - Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
7
  - Install: `curl -fsSL https://dssl-sable.vercel.app/install.sh | sh` (or the zip: https://dssl-sable.vercel.app/dssl-engine.zip)
8
+ - Version: 0.16.3 (engine), MIT license
9
  - Feature matrix vs CSS: see PARITY.md in the repo
10
  - For agents working ON the codebase: see AGENTS.md in the repo
11
 
 
64
  - Alpha on a named colour works: `white/12`, `currentColor/20`. It used to compile to `white/12px`.
65
  - The playground has a canvas.html tab, `?example=` links, lint warnings and an "as CSS" tab. The canvas keeps a short allow-list of tags and drops scripts, handlers, styles and ids.
66
 
67
+ ## 0.16.3
68
+ - Seven bugs found by writing the native spec (`native/spec/`) are fixed. An unquoted `url(http://x.com/a.png)` no longer loses the rest of the line to the comment stripper (also inside `raw { }`). A typo at the end of a token path (`color.ink.bogus`) is an `unknown value` error instead of quietly becoming `color.ink`. Nesting under a selector list joins every part: `.a, .b { .c { } }` is `.a .c, .b .c`, `hover { }` gives `.a:hover, .b:hover`, `.c, .d { }` under `.a` gives `.a .c, .a .d`, and `::before { }` gets one rule per part. A backslash escapes a quote in a value (`content: "a\"b"`). A nested `li` or any other HTML element name no longer gets a "did you mean link" warning.
69
+ - Two new warnings, both compile fine: an unknown `@layer` name (it is treated as `components`) and `radius: xl` when no `radius.xl` token exists.
70
+
71
  ## 0.16.2
72
  - `DSSL.lint`'s `css-loses` no longer judges the output of `dssl build` (its reset is built to sit under the engine), and ignores a rule whose value is the same as what DSSL wrote inline. On this site's docs page it reported 4,314 findings in 0.16.0; it now reports none from the site's own CSS.
73
 
 
136
 
137
  ## Editor support
138
 
139
+ The official extension (VS Code and VSCodium, same .vsix) bundles the real parser and resolver: diagnostics identical to the browser console, completions from the workspace token registry, hover docs for the semantic vocabulary. In VSCodium, search "DSSL" in the Extensions tab (it is on Open VSX: https://open-vsx.org/extension/ehs-studio/dssl-vscode). In VS Code, download `dssl-vscode/dssl-vscode-0.16.3.vsix` from the repo files, then Extensions panel -> ... -> Install from VSIX. Source lives in `dssl-vscode/` in the repo.
native/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ target/
native/Cargo.lock ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "aho-corasick"
7
+ version = "1.1.5"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
10
+ dependencies = [
11
+ "memchr",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "bit-set"
16
+ version = "0.8.0"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
19
+ dependencies = [
20
+ "bit-vec",
21
+ ]
22
+
23
+ [[package]]
24
+ name = "bit-vec"
25
+ version = "0.8.0"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
28
+
29
+ [[package]]
30
+ name = "dssl-native"
31
+ version = "0.1.0"
32
+ dependencies = [
33
+ "fancy-regex",
34
+ "serde_json",
35
+ ]
36
+
37
+ [[package]]
38
+ name = "fancy-regex"
39
+ version = "0.14.0"
40
+ source = "registry+https://github.com/rust-lang/crates.io-index"
41
+ checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
42
+ dependencies = [
43
+ "bit-set",
44
+ "regex-automata",
45
+ "regex-syntax",
46
+ ]
47
+
48
+ [[package]]
49
+ name = "itoa"
50
+ version = "1.0.18"
51
+ source = "registry+https://github.com/rust-lang/crates.io-index"
52
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
53
+
54
+ [[package]]
55
+ name = "memchr"
56
+ version = "2.8.3"
57
+ source = "registry+https://github.com/rust-lang/crates.io-index"
58
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
59
+
60
+ [[package]]
61
+ name = "proc-macro2"
62
+ version = "1.0.107"
63
+ source = "registry+https://github.com/rust-lang/crates.io-index"
64
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
65
+ dependencies = [
66
+ "unicode-ident",
67
+ ]
68
+
69
+ [[package]]
70
+ name = "quote"
71
+ version = "1.0.47"
72
+ source = "registry+https://github.com/rust-lang/crates.io-index"
73
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
74
+ dependencies = [
75
+ "proc-macro2",
76
+ ]
77
+
78
+ [[package]]
79
+ name = "regex-automata"
80
+ version = "0.4.18"
81
+ source = "registry+https://github.com/rust-lang/crates.io-index"
82
+ checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
83
+ dependencies = [
84
+ "aho-corasick",
85
+ "memchr",
86
+ "regex-syntax",
87
+ ]
88
+
89
+ [[package]]
90
+ name = "regex-syntax"
91
+ version = "0.8.11"
92
+ source = "registry+https://github.com/rust-lang/crates.io-index"
93
+ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
94
+
95
+ [[package]]
96
+ name = "serde"
97
+ version = "1.0.229"
98
+ source = "registry+https://github.com/rust-lang/crates.io-index"
99
+ checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
100
+ dependencies = [
101
+ "serde_core",
102
+ ]
103
+
104
+ [[package]]
105
+ name = "serde_core"
106
+ version = "1.0.229"
107
+ source = "registry+https://github.com/rust-lang/crates.io-index"
108
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
109
+ dependencies = [
110
+ "serde_derive",
111
+ ]
112
+
113
+ [[package]]
114
+ name = "serde_derive"
115
+ version = "1.0.229"
116
+ source = "registry+https://github.com/rust-lang/crates.io-index"
117
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
118
+ dependencies = [
119
+ "proc-macro2",
120
+ "quote",
121
+ "syn",
122
+ ]
123
+
124
+ [[package]]
125
+ name = "serde_json"
126
+ version = "1.0.154"
127
+ source = "registry+https://github.com/rust-lang/crates.io-index"
128
+ checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
129
+ dependencies = [
130
+ "itoa",
131
+ "memchr",
132
+ "serde",
133
+ "serde_core",
134
+ "zmij",
135
+ ]
136
+
137
+ [[package]]
138
+ name = "syn"
139
+ version = "3.0.9"
140
+ source = "registry+https://github.com/rust-lang/crates.io-index"
141
+ checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
142
+ dependencies = [
143
+ "proc-macro2",
144
+ "quote",
145
+ "unicode-ident",
146
+ ]
147
+
148
+ [[package]]
149
+ name = "unicode-ident"
150
+ version = "1.0.27"
151
+ source = "registry+https://github.com/rust-lang/crates.io-index"
152
+ checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
153
+
154
+ [[package]]
155
+ name = "zmij"
156
+ version = "1.0.23"
157
+ source = "registry+https://github.com/rust-lang/crates.io-index"
158
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
native/Cargo.toml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "dssl-native"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "DSSL reference compiler: a native, JavaScript-free implementation of the DSSL language"
6
+ license = "MIT"
7
+
8
+ [lib]
9
+ name = "dssl"
10
+ path = "src/lib.rs"
11
+
12
+ [[bin]]
13
+ name = "dssl-native"
14
+ path = "src/main.rs"
15
+
16
+ [dependencies]
17
+ serde_json = "1"
18
+ fancy-regex = "0.14"
native/README.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Native DSSL
2
+
3
+ DSSL today runs as a JavaScript engine. This folder is the start of DSSL as a language that does
4
+ not need JavaScript, the way CSS does not: a written specification, and a reference
5
+ implementation in Rust that a browser team can port or link.
6
+
7
+ ## What is here
8
+
9
+ | Path | What it is | State |
10
+ |---|---|---|
11
+ | `spec/` | The specification, chapters 1 to 12 and two generated appendices | Written (draft, against engine 0.16.3) |
12
+ | `data/vocabulary.json` | Every word list the language uses, in the engine's order | Generated |
13
+ | `src/`, `Cargo.toml` | The Rust crate (`dssl-native`) | A stub that builds. No parser yet. |
14
+ | `../scripts/test-native-spec.mjs` | Runs every spec example, checks the prose and the fixed bugs, checks the generated files | Passing |
15
+ | `../scripts/gen-native-vocab.mjs` | Writes the data file and the appendices from the engine | Working |
16
+
17
+ ## Honest status
18
+
19
+ - The spec covers the whole language, but not to the same depth everywhere. Syntax, values,
20
+ tokens, cascade, properties and motion are written precisely enough to implement from. The paint
21
+ half of texture, aura, slider and overlap is marked **Pending**: it is defined by the JavaScript
22
+ generators until it is written as prose (chapter 8).
23
+ - The Rust crate does nothing yet. Next is the parser (chapter 2), then the resolver (chapters 3
24
+ to 7), checked against the JavaScript engine over a corpus (chapter 10). Matching elements,
25
+ computing styles and paint are later levels (chapter 1.4).
26
+ - Nothing here is in a browser. Chapter 11 says what that would take.
27
+
28
+ ## Bugs the spec found in the engine
29
+
30
+ Checking the spec against the engine found eight places where the engine did not behave as the
31
+ language promises (chapter 12). Seven are fixed in 0.16.3 and `scripts/test-v0163.mjs` keeps them
32
+ fixed. The eighth (D-6, how base tokens carry over between files) needs a two-file test in a
33
+ browser before anyone decides whether it is a bug.
34
+
35
+ ## Commands
36
+
37
+ ```sh
38
+ node scripts/test-native-spec.mjs # spec examples, prose promises, fixed bugs, generated files
39
+ node scripts/gen-native-vocab.mjs # rewrite native/data and the appendices after an engine change
40
+ node scripts/gen-native-vocab.mjs --check # fail if they are out of date
41
+ cd native && cargo build # the crate (a stub today)
42
+ ```
native/data/vocabulary.json ADDED
@@ -0,0 +1,4567 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_about": "Generated by scripts/gen-native-vocab.mjs from public/dssl. Do not edit by hand. Array order is the engine's insertion order.",
3
+ "version": "0.16.3",
4
+ "stateWords": [
5
+ "hover",
6
+ "focus",
7
+ "focus-visible",
8
+ "active",
9
+ "disabled",
10
+ "checked",
11
+ "empty",
12
+ "target",
13
+ "required",
14
+ "invalid",
15
+ "valid",
16
+ "open",
17
+ "closed",
18
+ "placeholder-shown",
19
+ "read-only",
20
+ "read-write",
21
+ "enabled",
22
+ "indeterminate",
23
+ "first-child",
24
+ "last-child",
25
+ "only-child",
26
+ "first-of-type",
27
+ "last-of-type",
28
+ "link",
29
+ "visited",
30
+ "seen"
31
+ ],
32
+ "pseudoElements": [
33
+ "before",
34
+ "after",
35
+ "placeholder",
36
+ "selection",
37
+ "marker",
38
+ "first-line",
39
+ "first-letter",
40
+ "backdrop",
41
+ "file-selector-button",
42
+ "part",
43
+ "slotted"
44
+ ],
45
+ "vendorPseudoSource": "^-(webkit|moz|ms)-",
46
+ "cssKeywords": [
47
+ "inherit",
48
+ "initial",
49
+ "unset",
50
+ "revert",
51
+ "revert-layer",
52
+ "none",
53
+ "auto",
54
+ "currentcolor",
55
+ "transparent",
56
+ "from",
57
+ "stroke",
58
+ "markers",
59
+ "normal",
60
+ "black",
61
+ "silver",
62
+ "gray",
63
+ "white",
64
+ "maroon",
65
+ "red",
66
+ "purple",
67
+ "fuchsia",
68
+ "green",
69
+ "lime",
70
+ "olive",
71
+ "yellow",
72
+ "navy",
73
+ "blue",
74
+ "teal",
75
+ "aqua",
76
+ "orange",
77
+ "gold",
78
+ "pink",
79
+ "brown",
80
+ "violet",
81
+ "cyan",
82
+ "magenta",
83
+ "indigo",
84
+ "crimson",
85
+ "coral",
86
+ "tomato",
87
+ "goldenrod",
88
+ "khaki",
89
+ "slategray",
90
+ "slategrey",
91
+ "dimgray",
92
+ "dimgrey",
93
+ "lightgray",
94
+ "lightgrey",
95
+ "darkgray",
96
+ "darkgrey",
97
+ "gainsboro",
98
+ "whitesmoke",
99
+ "ghostwhite",
100
+ "ivory",
101
+ "beige",
102
+ "linen",
103
+ "seashell",
104
+ "snow",
105
+ "honeydew",
106
+ "mintcream",
107
+ "azure",
108
+ "aliceblue",
109
+ "lavender",
110
+ "canvas",
111
+ "canvastext",
112
+ "linktext",
113
+ "visitedtext",
114
+ "activetext",
115
+ "buttonface",
116
+ "buttontext",
117
+ "buttonborder",
118
+ "field",
119
+ "fieldtext",
120
+ "highlight",
121
+ "highlighttext",
122
+ "solid",
123
+ "dashed",
124
+ "dotted",
125
+ "double",
126
+ "groove",
127
+ "ridge",
128
+ "inset",
129
+ "outset",
130
+ "hidden",
131
+ "visible",
132
+ "block",
133
+ "inline",
134
+ "inline-block",
135
+ "flex",
136
+ "inline-flex",
137
+ "grid",
138
+ "inline-grid",
139
+ "flow-root",
140
+ "contents",
141
+ "table",
142
+ "table-row",
143
+ "table-cell",
144
+ "list-item",
145
+ "relative",
146
+ "absolute",
147
+ "fixed",
148
+ "sticky",
149
+ "static",
150
+ "start",
151
+ "end",
152
+ "center",
153
+ "left",
154
+ "right",
155
+ "between",
156
+ "around",
157
+ "evenly",
158
+ "baseline",
159
+ "stretch",
160
+ "space-between",
161
+ "space-around",
162
+ "space-evenly",
163
+ "wrap",
164
+ "nowrap",
165
+ "reverse",
166
+ "row",
167
+ "column",
168
+ "row-reverse",
169
+ "column-reverse",
170
+ "bold",
171
+ "bolder",
172
+ "lighter",
173
+ "italic",
174
+ "oblique",
175
+ "small-caps",
176
+ "uppercase",
177
+ "lowercase",
178
+ "capitalize",
179
+ "underline",
180
+ "overline",
181
+ "line-through",
182
+ "blink",
183
+ "ellipsis",
184
+ "clip",
185
+ "break-word",
186
+ "anywhere",
187
+ "pre",
188
+ "pre-wrap",
189
+ "pre-line",
190
+ "subpixel-antialiased",
191
+ "antialiased",
192
+ "pointer",
193
+ "progress",
194
+ "default",
195
+ "move",
196
+ "text",
197
+ "wait",
198
+ "help",
199
+ "grab",
200
+ "grabbing",
201
+ "not-allowed",
202
+ "zoom-in",
203
+ "zoom-out",
204
+ "copy",
205
+ "alias",
206
+ "crosshair",
207
+ "serif",
208
+ "sans-serif",
209
+ "monospace",
210
+ "cursive",
211
+ "fantasy",
212
+ "system-ui",
213
+ "ui-sans-serif",
214
+ "ui-monospace",
215
+ "ui-serif",
216
+ "ui-rounded",
217
+ "-apple-system",
218
+ "Roboto",
219
+ "Helvetica",
220
+ "Arial",
221
+ "Menlo",
222
+ "Consolas",
223
+ "SFMono-Regular",
224
+ "Liberation-Mono",
225
+ "Inter",
226
+ "Noto-Sans",
227
+ "Ubuntu",
228
+ "Cantarell",
229
+ "Oxygen",
230
+ "Fira-Sans",
231
+ "Droid-Sans",
232
+ "border-box",
233
+ "content-box",
234
+ "padding-box",
235
+ "fill-box",
236
+ "stroke-box",
237
+ "view-box",
238
+ "no-repeat",
239
+ "repeat-x",
240
+ "repeat-y",
241
+ "space",
242
+ "round",
243
+ "scroll",
244
+ "local",
245
+ "contain",
246
+ "cover",
247
+ "fill",
248
+ "fit-content",
249
+ "min-content",
250
+ "max-content",
251
+ "minmax",
252
+ "repeat",
253
+ "clamp",
254
+ "calc",
255
+ "var",
256
+ "url",
257
+ "env",
258
+ "attr",
259
+ "auto-fill",
260
+ "auto-fit",
261
+ "subgrid",
262
+ "dense",
263
+ "span",
264
+ "scaleX",
265
+ "scaleY",
266
+ "translateX",
267
+ "translateY",
268
+ "translate",
269
+ "rotate",
270
+ "skew",
271
+ "skewX",
272
+ "skewY",
273
+ "matrix",
274
+ "ease",
275
+ "ease-in",
276
+ "ease-out",
277
+ "ease-in-out",
278
+ "linear",
279
+ "step-start",
280
+ "step-end",
281
+ "steps",
282
+ "cubic-bezier",
283
+ "spring",
284
+ "bounce",
285
+ "smooth",
286
+ "snap",
287
+ "forwards",
288
+ "backwards",
289
+ "both",
290
+ "infinite",
291
+ "alternate",
292
+ "alternate-reverse",
293
+ "running",
294
+ "paused",
295
+ "safe",
296
+ "unsafe",
297
+ "legacy",
298
+ "loose",
299
+ "strict",
300
+ "spell-out",
301
+ "letters",
302
+ "isolate",
303
+ "isolation",
304
+ "blend",
305
+ "multiply",
306
+ "screen",
307
+ "overlay",
308
+ "darken",
309
+ "lighten",
310
+ "color-dodge",
311
+ "color-burn",
312
+ "hard-light",
313
+ "soft-light",
314
+ "difference",
315
+ "exclusion",
316
+ "hue",
317
+ "saturation",
318
+ "color",
319
+ "luminosity",
320
+ "plus-lighter",
321
+ "blur",
322
+ "brightness",
323
+ "contrast",
324
+ "grayscale",
325
+ "invert",
326
+ "opacity",
327
+ "sepia",
328
+ "saturate",
329
+ "drop-shadow",
330
+ "pointer coarse fine hover interactive",
331
+ "first",
332
+ "last",
333
+ "odd",
334
+ "even",
335
+ "all",
336
+ "portrait",
337
+ "landscape",
338
+ "print",
339
+ "and",
340
+ "not",
341
+ "only",
342
+ "inline-size",
343
+ "size",
344
+ "scroll-state",
345
+ "snapped",
346
+ "physical",
347
+ "logical",
348
+ "in",
349
+ "oklab",
350
+ "oklch",
351
+ "srgb",
352
+ "srgb-linear",
353
+ "display-p3",
354
+ "a98-rgb",
355
+ "prophoto-rgb",
356
+ "rec2020",
357
+ "xyz",
358
+ "xyz-d50",
359
+ "xyz-d65",
360
+ "shorter",
361
+ "longer",
362
+ "deg",
363
+ "grad",
364
+ "rad",
365
+ "turn",
366
+ "to",
367
+ "top",
368
+ "bottom",
369
+ "above",
370
+ "below",
371
+ "closest-side",
372
+ "farthest-side",
373
+ "ellipse",
374
+ "circle",
375
+ "at",
376
+ "plaintext",
377
+ "sideways",
378
+ "upright",
379
+ "mixed",
380
+ "ltr",
381
+ "rtl",
382
+ "transform",
383
+ "-webkit-box",
384
+ "-webkit-inline-box",
385
+ "thin",
386
+ "thick",
387
+ "medium",
388
+ "separate",
389
+ "collapse",
390
+ "vertical",
391
+ "horizontal",
392
+ "balance",
393
+ "pretty",
394
+ "orphans",
395
+ "manual",
396
+ "pixelated",
397
+ "crisp-edges",
398
+ "optimizeSpeed",
399
+ "optimizeLegibility",
400
+ "geometricPrecision",
401
+ "flex-start",
402
+ "flex-end",
403
+ "self-start",
404
+ "self-end",
405
+ "snap-block",
406
+ "snap-inline",
407
+ "swap",
408
+ "fallback",
409
+ "optional",
410
+ "-apple-pay-button",
411
+ "-infinity",
412
+ "-moz-mac-unified-toolbar",
413
+ "-moz-win-borderless-glass",
414
+ "-moz-win-browsertabbar-toolbox",
415
+ "-moz-win-communications-toolbox",
416
+ "-moz-win-communicationstext",
417
+ "-moz-win-exclude-glass",
418
+ "-moz-win-glass",
419
+ "-moz-win-media-toolbox",
420
+ "-moz-win-mediatext",
421
+ "-moz-window-button-box",
422
+ "-moz-window-button-box-maximized",
423
+ "-moz-window-button-close",
424
+ "-moz-window-button-maximize",
425
+ "-moz-window-button-minimize",
426
+ "-moz-window-button-restore",
427
+ "-moz-window-frame-bottom",
428
+ "-moz-window-frame-left",
429
+ "-moz-window-frame-right",
430
+ "-moz-window-titlebar",
431
+ "-moz-window-titlebar-maximized",
432
+ "-ms-autohiding-scrollbar",
433
+ "-n",
434
+ "-n-",
435
+ "accumulate",
436
+ "active",
437
+ "add",
438
+ "after",
439
+ "all-petite-caps",
440
+ "all-scroll",
441
+ "all-small-caps",
442
+ "allow-discrete",
443
+ "allow-end",
444
+ "allow-keywords",
445
+ "alpha",
446
+ "alphabetic",
447
+ "always",
448
+ "anchor-center",
449
+ "anchors-valid",
450
+ "anchors-visible",
451
+ "angle",
452
+ "annotation",
453
+ "antiquewhite",
454
+ "any",
455
+ "aquamarine",
456
+ "arcs",
457
+ "auto-add",
458
+ "auto-flow",
459
+ "auto-phrase",
460
+ "avoid",
461
+ "avoid-column",
462
+ "avoid-page",
463
+ "avoid-region",
464
+ "bar",
465
+ "before",
466
+ "bevel",
467
+ "bidi-override",
468
+ "bisque",
469
+ "blanchedalmond",
470
+ "blank",
471
+ "block-axis",
472
+ "block-end",
473
+ "block-start",
474
+ "blueviolet",
475
+ "border",
476
+ "border-area",
477
+ "both-edges",
478
+ "bottom-center",
479
+ "bottom-left",
480
+ "bottom-left-corner",
481
+ "bottom-right",
482
+ "bottom-right-corner",
483
+ "box-decoration",
484
+ "break-all",
485
+ "break-spaces",
486
+ "bt",
487
+ "burlywood",
488
+ "butt",
489
+ "button",
490
+ "button-arrow-down",
491
+ "button-arrow-next",
492
+ "button-arrow-previous",
493
+ "button-arrow-up",
494
+ "button-bevel",
495
+ "button-focus",
496
+ "cadetblue",
497
+ "cap",
498
+ "cap-height",
499
+ "caption",
500
+ "caret",
501
+ "cell",
502
+ "central",
503
+ "ch-width",
504
+ "chained",
505
+ "character-variant",
506
+ "chartreuse",
507
+ "checkbox",
508
+ "checkbox-container",
509
+ "checkbox-label",
510
+ "checkmenuitem",
511
+ "chocolate",
512
+ "clear",
513
+ "clone",
514
+ "close-quote",
515
+ "closest-corner",
516
+ "cm",
517
+ "col-resize",
518
+ "common-ligatures",
519
+ "compact",
520
+ "condensed",
521
+ "consistent",
522
+ "constrained",
523
+ "content",
524
+ "content-block-size",
525
+ "content-height",
526
+ "content-inline-size",
527
+ "content-width",
528
+ "context-fill",
529
+ "context-menu",
530
+ "context-stroke",
531
+ "contextual",
532
+ "cornflowerblue",
533
+ "cornsilk",
534
+ "cyclic",
535
+ "dark",
536
+ "darkblue",
537
+ "darkcyan",
538
+ "darkgoldenrod",
539
+ "darkgreen",
540
+ "darkkhaki",
541
+ "darkmagenta",
542
+ "darkolivegreen",
543
+ "darkorange",
544
+ "darkorchid",
545
+ "darkred",
546
+ "darksalmon",
547
+ "darkseagreen",
548
+ "darkslateblue",
549
+ "darkslategray",
550
+ "darkslategrey",
551
+ "darkturquoise",
552
+ "darkviolet",
553
+ "decreasing",
554
+ "deeppink",
555
+ "deepskyblue",
556
+ "default-button",
557
+ "definite-first",
558
+ "destination-atop",
559
+ "destination-in",
560
+ "destination-out",
561
+ "destination-over",
562
+ "diagonal-fractions",
563
+ "digits",
564
+ "disabled",
565
+ "discretionary-ligatures",
566
+ "display-p3-linear",
567
+ "dodgerblue",
568
+ "dot",
569
+ "double-circle",
570
+ "down",
571
+ "drag",
572
+ "dualbutton",
573
+ "e-resize",
574
+ "each-line",
575
+ "economy",
576
+ "edges",
577
+ "element",
578
+ "em",
579
+ "embed",
580
+ "emoji",
581
+ "enabled",
582
+ "entry",
583
+ "entry-crossing",
584
+ "evenodd",
585
+ "ew-resize",
586
+ "ex",
587
+ "ex-height",
588
+ "exact",
589
+ "exclude",
590
+ "exit",
591
+ "exit-crossing",
592
+ "expanded",
593
+ "extra-condensed",
594
+ "extra-expanded",
595
+ "false",
596
+ "fangsong",
597
+ "farthest-corner",
598
+ "fill-opacity",
599
+ "filled",
600
+ "firebrick",
601
+ "first-letter",
602
+ "fixed-position",
603
+ "flat",
604
+ "flex-flow",
605
+ "flex-visual",
606
+ "flip",
607
+ "flip-block",
608
+ "flip-inline",
609
+ "flip-start",
610
+ "floralwhite",
611
+ "flow",
612
+ "force-end",
613
+ "forestgreen",
614
+ "frequency",
615
+ "from-font",
616
+ "from-image",
617
+ "full-size-kana",
618
+ "full-width",
619
+ "grammar-error",
620
+ "greenyellow",
621
+ "grey",
622
+ "grid-columns",
623
+ "grid-order",
624
+ "grid-rows",
625
+ "grippers",
626
+ "groupbox",
627
+ "grow",
628
+ "hanging",
629
+ "height",
630
+ "hide",
631
+ "historical-forms",
632
+ "historical-ligatures",
633
+ "horizontal-tb",
634
+ "hotpink",
635
+ "hsl",
636
+ "hwb",
637
+ "i",
638
+ "ic",
639
+ "ic-height",
640
+ "ic-width",
641
+ "icon",
642
+ "ideograph-alpha",
643
+ "ideograph-numeric",
644
+ "ideograph-parenthesis",
645
+ "ideograph-space",
646
+ "ideographic",
647
+ "ideographic-ink",
648
+ "ignore",
649
+ "in-flow",
650
+ "inactive",
651
+ "increasing",
652
+ "indianred",
653
+ "inert",
654
+ "infinity",
655
+ "inline-axis",
656
+ "inline-end",
657
+ "inline-list-item",
658
+ "inline-start",
659
+ "inline-table",
660
+ "inner-spin-button",
661
+ "inside",
662
+ "integer",
663
+ "inter-character",
664
+ "inter-word",
665
+ "intersect",
666
+ "isolate-override",
667
+ "jis04",
668
+ "jis78",
669
+ "jis83",
670
+ "jis90",
671
+ "jump-both",
672
+ "jump-end",
673
+ "jump-none",
674
+ "jump-start",
675
+ "justify",
676
+ "keep-all",
677
+ "lab",
678
+ "large",
679
+ "larger",
680
+ "lavenderblush",
681
+ "lawngreen",
682
+ "layout",
683
+ "lch",
684
+ "leading-spaces",
685
+ "ledger",
686
+ "left-bottom",
687
+ "left-middle",
688
+ "left-top",
689
+ "legal",
690
+ "lemonchiffon",
691
+ "length",
692
+ "letter",
693
+ "lh",
694
+ "light",
695
+ "lightblue",
696
+ "lightcoral",
697
+ "lightcyan",
698
+ "lightgoldenrodyellow",
699
+ "lightgreen",
700
+ "lightpink",
701
+ "lightsalmon",
702
+ "lightseagreen",
703
+ "lightskyblue",
704
+ "lightslategray",
705
+ "lightslategrey",
706
+ "lightsteelblue",
707
+ "lightyellow",
708
+ "limegreen",
709
+ "lining-nums",
710
+ "listbox",
711
+ "listitem",
712
+ "literal-punctuation",
713
+ "lr",
714
+ "luminance",
715
+ "mandatory",
716
+ "manipulation",
717
+ "margin-box",
718
+ "match-element",
719
+ "match-parent",
720
+ "match-source",
721
+ "math",
722
+ "math-auto",
723
+ "mathematical",
724
+ "maximum",
725
+ "media-controls-background",
726
+ "media-controls-fullscreen-background",
727
+ "media-current-time-display",
728
+ "media-enter-fullscreen-button",
729
+ "media-exit-fullscreen-button",
730
+ "media-fullscreen-button",
731
+ "media-mute-button",
732
+ "media-overlay-play-button",
733
+ "media-play-button",
734
+ "media-seek-back-button",
735
+ "media-seek-forward-button",
736
+ "media-slider",
737
+ "media-sliderthumb",
738
+ "media-time-remaining-display",
739
+ "media-toggle-closed-captions-button",
740
+ "media-volume-slider",
741
+ "media-volume-slider-container",
742
+ "media-volume-sliderthumb",
743
+ "mediumaquamarine",
744
+ "mediumblue",
745
+ "mediumorchid",
746
+ "mediumpurple",
747
+ "mediumseagreen",
748
+ "mediumslateblue",
749
+ "mediumspringgreen",
750
+ "mediumturquoise",
751
+ "mediumvioletred",
752
+ "menu",
753
+ "menuarrow",
754
+ "menubar",
755
+ "menucheckbox",
756
+ "menuimage",
757
+ "menuitem",
758
+ "menuitemtext",
759
+ "menulist",
760
+ "menulist-button",
761
+ "menulist-text",
762
+ "menulist-textfield",
763
+ "menupopup",
764
+ "menuradio",
765
+ "menuseparator",
766
+ "message-box",
767
+ "meter",
768
+ "meterbar",
769
+ "meterchunk",
770
+ "middle",
771
+ "midnightblue",
772
+ "mistyrose",
773
+ "miter",
774
+ "miter-clip",
775
+ "mm",
776
+ "moccasin",
777
+ "most-block-size",
778
+ "most-height",
779
+ "most-inline-size",
780
+ "most-width",
781
+ "ms",
782
+ "multiple",
783
+ "n-",
784
+ "n-resize",
785
+ "navajowhite",
786
+ "ne-resize",
787
+ "nearest",
788
+ "nesw-resize",
789
+ "never",
790
+ "next",
791
+ "no-clip",
792
+ "no-close-quote",
793
+ "no-common-ligatures",
794
+ "no-contextual",
795
+ "no-discretionary-ligatures",
796
+ "no-drag",
797
+ "no-drop",
798
+ "no-historical-ligatures",
799
+ "no-limit",
800
+ "no-open-quote",
801
+ "no-overflow",
802
+ "no-punctuation",
803
+ "non-rotation",
804
+ "non-scaling-size",
805
+ "non-scaling-stroke",
806
+ "nonzero",
807
+ "notch",
808
+ "ns-resize",
809
+ "number",
810
+ "numeric",
811
+ "numeric-only",
812
+ "nw-resize",
813
+ "nwse-resize",
814
+ "objects",
815
+ "off",
816
+ "oldlace",
817
+ "oldstyle-nums",
818
+ "olivedrab",
819
+ "on",
820
+ "open",
821
+ "open-quote",
822
+ "or",
823
+ "orangered",
824
+ "orchid",
825
+ "ordered",
826
+ "ordinal",
827
+ "ornaments",
828
+ "outside",
829
+ "over",
830
+ "pack",
831
+ "padding",
832
+ "page",
833
+ "paint",
834
+ "painted",
835
+ "palegoldenrod",
836
+ "palegreen",
837
+ "paleturquoise",
838
+ "palevioletred",
839
+ "pan-down",
840
+ "pan-left",
841
+ "pan-right",
842
+ "pan-up",
843
+ "pan-x",
844
+ "pan-y",
845
+ "papayawhip",
846
+ "pause",
847
+ "pc",
848
+ "peachpuff",
849
+ "per-line",
850
+ "per-line-all",
851
+ "peru",
852
+ "petite-caps",
853
+ "pi",
854
+ "pinch-zoom",
855
+ "play",
856
+ "play-backwards",
857
+ "play-forwards",
858
+ "play-once",
859
+ "plum",
860
+ "plus-darker",
861
+ "position",
862
+ "powderblue",
863
+ "preserve",
864
+ "preserve-3d",
865
+ "preserve-breaks",
866
+ "preserve-parent-color",
867
+ "preserve-spaces",
868
+ "progress-bar",
869
+ "progress-bar-value",
870
+ "progressbar",
871
+ "progressbar-vertical",
872
+ "progresschunk",
873
+ "progresschunk-vertical",
874
+ "proportional-nums",
875
+ "proportional-width",
876
+ "proximity",
877
+ "pt",
878
+ "push-button",
879
+ "px",
880
+ "radio",
881
+ "radio-container",
882
+ "radio-label",
883
+ "radiomenuitem",
884
+ "railed",
885
+ "range",
886
+ "range-thumb",
887
+ "raw-string",
888
+ "read-only",
889
+ "read-write",
890
+ "read-write-plaintext-only",
891
+ "rebeccapurple",
892
+ "recto",
893
+ "region",
894
+ "rem",
895
+ "replace",
896
+ "replay",
897
+ "reset",
898
+ "resizer",
899
+ "resizerpanel",
900
+ "right-bottom",
901
+ "right-middle",
902
+ "right-top",
903
+ "rl",
904
+ "rlh",
905
+ "root",
906
+ "rosybrown",
907
+ "row-resize",
908
+ "royalblue",
909
+ "ruby",
910
+ "ruby-base",
911
+ "ruby-base-container",
912
+ "ruby-text",
913
+ "ruby-text-container",
914
+ "run-in",
915
+ "s",
916
+ "s-resize",
917
+ "saddlebrown",
918
+ "salmon",
919
+ "sandybrown",
920
+ "scale-down",
921
+ "scale-horizontal",
922
+ "scale-vertical",
923
+ "scalethumb-horizontal",
924
+ "scalethumb-vertical",
925
+ "scalethumbend",
926
+ "scalethumbstart",
927
+ "scalethumbtick",
928
+ "scoop",
929
+ "scroll-position",
930
+ "scrollbar",
931
+ "scrollbarbutton-down",
932
+ "scrollbarbutton-left",
933
+ "scrollbarbutton-right",
934
+ "scrollbarbutton-up",
935
+ "scrollbarthumb-horizontal",
936
+ "scrollbarthumb-vertical",
937
+ "scrollbartrack-horizontal",
938
+ "scrollbartrack-vertical",
939
+ "se-resize",
940
+ "seagreen",
941
+ "searchfield",
942
+ "searchfield-cancel-button",
943
+ "searchfield-decoration",
944
+ "searchfield-results-button",
945
+ "searchfield-results-decoration",
946
+ "select",
947
+ "select-after",
948
+ "select-all",
949
+ "select-before",
950
+ "select-menu",
951
+ "select-same",
952
+ "self",
953
+ "self-block",
954
+ "self-block-end",
955
+ "self-block-start",
956
+ "self-inline",
957
+ "self-inline-end",
958
+ "self-inline-start",
959
+ "semi-condensed",
960
+ "semi-expanded",
961
+ "separator",
962
+ "sesame",
963
+ "sheet",
964
+ "show",
965
+ "shrink",
966
+ "sides",
967
+ "sideways-lr",
968
+ "sideways-rl",
969
+ "sienna",
970
+ "simplified",
971
+ "single",
972
+ "skyblue",
973
+ "slashed-zero",
974
+ "slateblue",
975
+ "slice",
976
+ "slider-horizontal",
977
+ "slider-vertical",
978
+ "sliderthumb-horizontal",
979
+ "sliderthumb-vertical",
980
+ "small",
981
+ "small-caption",
982
+ "smaller",
983
+ "source-atop",
984
+ "source-in",
985
+ "source-order",
986
+ "source-out",
987
+ "source-over",
988
+ "space-all",
989
+ "space-first",
990
+ "spaces",
991
+ "span-all",
992
+ "span-block-end",
993
+ "span-block-start",
994
+ "span-bottom",
995
+ "span-end",
996
+ "span-inline-end",
997
+ "span-inline-start",
998
+ "span-left",
999
+ "span-right",
1000
+ "span-self-block-end",
1001
+ "span-self-block-start",
1002
+ "span-self-end",
1003
+ "span-self-inline-end",
1004
+ "span-self-inline-start",
1005
+ "span-self-start",
1006
+ "span-start",
1007
+ "span-top",
1008
+ "span-x-end",
1009
+ "span-x-self-end",
1010
+ "span-x-self-start",
1011
+ "span-x-start",
1012
+ "span-y-end",
1013
+ "span-y-self-end",
1014
+ "span-y-self-start",
1015
+ "span-y-start",
1016
+ "spelling-error",
1017
+ "spinner",
1018
+ "spinner-downbutton",
1019
+ "spinner-textfield",
1020
+ "spinner-upbutton",
1021
+ "splitter",
1022
+ "springgreen",
1023
+ "square",
1024
+ "square-button",
1025
+ "squircle",
1026
+ "stable",
1027
+ "stacked-fractions",
1028
+ "standard",
1029
+ "status-bar",
1030
+ "statusbar",
1031
+ "statusbarpanel",
1032
+ "steelblue",
1033
+ "stretch-to-fit",
1034
+ "string",
1035
+ "stroke-opacity",
1036
+ "style",
1037
+ "styleset",
1038
+ "stylistic",
1039
+ "sub",
1040
+ "subtract",
1041
+ "super",
1042
+ "sw-resize",
1043
+ "swash",
1044
+ "symbolic",
1045
+ "tab",
1046
+ "tab-scroll-arrow-back",
1047
+ "tab-scroll-arrow-forward",
1048
+ "table-caption",
1049
+ "table-column",
1050
+ "table-column-group",
1051
+ "table-footer-group",
1052
+ "table-header-group",
1053
+ "table-row-group",
1054
+ "tabpanel",
1055
+ "tabpanels",
1056
+ "tabular-nums",
1057
+ "tan",
1058
+ "tb",
1059
+ "text-after-edge",
1060
+ "text-before-edge",
1061
+ "text-bottom",
1062
+ "text-top",
1063
+ "textarea",
1064
+ "textfield",
1065
+ "textfield-multiline",
1066
+ "thistle",
1067
+ "time",
1068
+ "titling-caps",
1069
+ "to-zero",
1070
+ "toolbar",
1071
+ "toolbarbutton",
1072
+ "toolbarbutton-dropdown",
1073
+ "toolbargripper",
1074
+ "toolbox",
1075
+ "tooltip",
1076
+ "top-center",
1077
+ "top-left",
1078
+ "top-left-corner",
1079
+ "top-right",
1080
+ "top-right-corner",
1081
+ "touch",
1082
+ "traditional",
1083
+ "trailing-spaces",
1084
+ "treeheader",
1085
+ "treeheadercell",
1086
+ "treeheadersortarrow",
1087
+ "treeitem",
1088
+ "treeline",
1089
+ "treetwisty",
1090
+ "treetwistyopen",
1091
+ "treeview",
1092
+ "triangle",
1093
+ "trim-both",
1094
+ "trim-end",
1095
+ "trim-start",
1096
+ "true",
1097
+ "turquoise",
1098
+ "ultra-condensed",
1099
+ "ultra-expanded",
1100
+ "under",
1101
+ "underscore",
1102
+ "unicase",
1103
+ "unicode",
1104
+ "up",
1105
+ "vb",
1106
+ "verso",
1107
+ "vertical-lr",
1108
+ "vertical-rl",
1109
+ "vertical-text",
1110
+ "vertical-to-horizontal",
1111
+ "vh",
1112
+ "vi",
1113
+ "vmax",
1114
+ "vmin",
1115
+ "vw",
1116
+ "w-resize",
1117
+ "wavy",
1118
+ "weight",
1119
+ "wheat",
1120
+ "width",
1121
+ "wrap-reverse",
1122
+ "write-only",
1123
+ "x",
1124
+ "x-end",
1125
+ "x-large",
1126
+ "x-self-end",
1127
+ "x-self-start",
1128
+ "x-small",
1129
+ "x-start",
1130
+ "xor",
1131
+ "xx-large",
1132
+ "xx-small",
1133
+ "xxx-large",
1134
+ "y",
1135
+ "y-end",
1136
+ "y-self-end",
1137
+ "y-self-start",
1138
+ "y-start",
1139
+ "yellowgreen",
1140
+ "z",
1141
+ "zoom"
1142
+ ],
1143
+ "knownProps": [
1144
+ "all",
1145
+ "appearance",
1146
+ "accent-color",
1147
+ "align-content",
1148
+ "align-items",
1149
+ "align-self",
1150
+ "align-tracks",
1151
+ "aspect-ratio",
1152
+ "backdrop-filter",
1153
+ "backface-visibility",
1154
+ "background",
1155
+ "background-attachment",
1156
+ "background-blend-mode",
1157
+ "background-clip",
1158
+ "background-color",
1159
+ "background-image",
1160
+ "background-origin",
1161
+ "background-position",
1162
+ "background-repeat",
1163
+ "background-size",
1164
+ "block-size",
1165
+ "border",
1166
+ "border-block",
1167
+ "border-block-color",
1168
+ "border-block-end",
1169
+ "border-block-end-color",
1170
+ "border-block-end-style",
1171
+ "border-block-end-width",
1172
+ "border-block-start",
1173
+ "border-block-start-color",
1174
+ "border-block-start-style",
1175
+ "border-block-start-width",
1176
+ "border-block-style",
1177
+ "border-block-width",
1178
+ "border-bottom",
1179
+ "border-bottom-color",
1180
+ "border-bottom-left-radius",
1181
+ "border-bottom-right-radius",
1182
+ "border-bottom-style",
1183
+ "border-bottom-width",
1184
+ "border-collapse",
1185
+ "border-color",
1186
+ "border-end-end-radius",
1187
+ "border-end-start-radius",
1188
+ "border-image",
1189
+ "border-image-outset",
1190
+ "border-image-repeat",
1191
+ "border-image-slice",
1192
+ "border-image-source",
1193
+ "border-image-width",
1194
+ "border-inline",
1195
+ "border-inline-color",
1196
+ "border-inline-end",
1197
+ "border-inline-end-color",
1198
+ "border-inline-end-style",
1199
+ "border-inline-end-width",
1200
+ "border-inline-start",
1201
+ "border-inline-start-color",
1202
+ "border-inline-start-style",
1203
+ "border-inline-start-width",
1204
+ "border-inline-style",
1205
+ "border-inline-width",
1206
+ "border-left",
1207
+ "border-left-color",
1208
+ "border-left-style",
1209
+ "border-left-width",
1210
+ "border-radius",
1211
+ "border-right",
1212
+ "border-right-color",
1213
+ "border-right-style",
1214
+ "border-right-width",
1215
+ "border-spacing",
1216
+ "border-start-end-radius",
1217
+ "border-start-start-radius",
1218
+ "border-style",
1219
+ "border-top",
1220
+ "border-top-color",
1221
+ "border-top-left-radius",
1222
+ "border-top-right-radius",
1223
+ "border-top-style",
1224
+ "border-top-width",
1225
+ "border-width",
1226
+ "bottom",
1227
+ "counter-reset",
1228
+ "counter-increment",
1229
+ "counter-set",
1230
+ "box-decoration-break",
1231
+ "box-shadow",
1232
+ "box-sizing",
1233
+ "break-after",
1234
+ "break-before",
1235
+ "break-inside",
1236
+ "caret-color",
1237
+ "caret-shape",
1238
+ "clear",
1239
+ "clip",
1240
+ "clip-path",
1241
+ "color",
1242
+ "color-scheme",
1243
+ "column-count",
1244
+ "column-fill",
1245
+ "column-gap",
1246
+ "column-rule",
1247
+ "column-rule-color",
1248
+ "column-rule-style",
1249
+ "column-rule-width",
1250
+ "column-span",
1251
+ "column-width",
1252
+ "columns",
1253
+ "contain",
1254
+ "contain-intrinsic-size",
1255
+ "container",
1256
+ "container-name",
1257
+ "container-type",
1258
+ "content",
1259
+ "content-visibility",
1260
+ "cursor",
1261
+ "caret",
1262
+ "display",
1263
+ "empty-cells",
1264
+ "filter",
1265
+ "flex",
1266
+ "flex-basis",
1267
+ "flex-direction",
1268
+ "flex-flow",
1269
+ "flex-grow",
1270
+ "flex-shrink",
1271
+ "flex-wrap",
1272
+ "float",
1273
+ "fill",
1274
+ "fill-rule",
1275
+ "fill-opacity",
1276
+ "stroke",
1277
+ "stroke-dasharray",
1278
+ "stroke-dashoffset",
1279
+ "stroke-linecap",
1280
+ "stroke-linejoin",
1281
+ "stroke-opacity",
1282
+ "stroke-width",
1283
+ "font",
1284
+ "font-family",
1285
+ "font-feature-settings",
1286
+ "font-kerning",
1287
+ "font-language-override",
1288
+ "font-optical-sizing",
1289
+ "font-palette",
1290
+ "font-size",
1291
+ "font-size-adjust",
1292
+ "font-stretch",
1293
+ "font-style",
1294
+ "font-synthesis",
1295
+ "font-variant",
1296
+ "font-variant-caps",
1297
+ "font-variant-east-asian",
1298
+ "font-variant-ligatures",
1299
+ "font-variant-numeric",
1300
+ "font-variant-position",
1301
+ "font-variation-settings",
1302
+ "font-weight",
1303
+ "forced-color-adjust",
1304
+ "gap",
1305
+ "grid",
1306
+ "grid-area",
1307
+ "grid-auto-columns",
1308
+ "grid-auto-flow",
1309
+ "grid-auto-rows",
1310
+ "grid-column",
1311
+ "grid-column-end",
1312
+ "grid-column-gap",
1313
+ "grid-column-start",
1314
+ "grid-gap",
1315
+ "grid-row",
1316
+ "grid-row-end",
1317
+ "grid-row-gap",
1318
+ "grid-row-start",
1319
+ "grid-template",
1320
+ "grid-template-areas",
1321
+ "grid-template-columns",
1322
+ "grid-template-rows",
1323
+ "hanging-punctuation",
1324
+ "height",
1325
+ "hyphenate-character",
1326
+ "hyphenate-limit-chars",
1327
+ "hyphens",
1328
+ "image-orientation",
1329
+ "image-rendering",
1330
+ "inline-size",
1331
+ "inset",
1332
+ "inset-block",
1333
+ "inset-block-end",
1334
+ "inset-block-start",
1335
+ "inset-inline",
1336
+ "inset-inline-end",
1337
+ "inset-inline-start",
1338
+ "isolation",
1339
+ "justify-content",
1340
+ "justify-items",
1341
+ "justify-self",
1342
+ "justify-tracks",
1343
+ "left",
1344
+ "letter-spacing",
1345
+ "line-break",
1346
+ "line-height",
1347
+ "list-style",
1348
+ "list-style-image",
1349
+ "list-style-position",
1350
+ "list-style-type",
1351
+ "margin",
1352
+ "margin-block",
1353
+ "margin-block-end",
1354
+ "margin-block-start",
1355
+ "margin-bottom",
1356
+ "margin-inline",
1357
+ "margin-inline-end",
1358
+ "margin-inline-start",
1359
+ "margin-left",
1360
+ "margin-right",
1361
+ "margin-top",
1362
+ "margin-trim",
1363
+ "mask",
1364
+ "mask-border",
1365
+ "mask-border-mode",
1366
+ "mask-border-outset",
1367
+ "mask-border-repeat",
1368
+ "mask-border-slice",
1369
+ "mask-border-source",
1370
+ "mask-border-width",
1371
+ "mask-clip",
1372
+ "mask-composite",
1373
+ "mask-image",
1374
+ "mask-mode",
1375
+ "mask-origin",
1376
+ "mask-position",
1377
+ "mask-repeat",
1378
+ "mask-size",
1379
+ "max-height",
1380
+ "max-inline-size",
1381
+ "max-width",
1382
+ "min-block-size",
1383
+ "min-height",
1384
+ "min-inline-size",
1385
+ "min-width",
1386
+ "mix-blend-mode",
1387
+ "object-fit",
1388
+ "object-position",
1389
+ "offset",
1390
+ "offset-anchor",
1391
+ "offset-distance",
1392
+ "offset-path",
1393
+ "offset-rotate",
1394
+ "opacity",
1395
+ "order",
1396
+ "outline",
1397
+ "outline-color",
1398
+ "outline-offset",
1399
+ "outline-style",
1400
+ "outline-width",
1401
+ "overflow",
1402
+ "overflow-anchor",
1403
+ "overflow-clip-margin",
1404
+ "overflow-wrap",
1405
+ "overflow-x",
1406
+ "overflow-y",
1407
+ "overscroll-behavior",
1408
+ "overscroll-behavior-block",
1409
+ "overscroll-behavior-inline",
1410
+ "overscroll-behavior-x",
1411
+ "overscroll-behavior-y",
1412
+ "padding",
1413
+ "padding-block",
1414
+ "padding-block-end",
1415
+ "padding-block-start",
1416
+ "padding-bottom",
1417
+ "padding-inline",
1418
+ "padding-inline-end",
1419
+ "padding-inline-start",
1420
+ "padding-left",
1421
+ "padding-right",
1422
+ "padding-top",
1423
+ "page-break-after",
1424
+ "page-break-before",
1425
+ "page-break-inside",
1426
+ "paint-order",
1427
+ "perspective",
1428
+ "perspective-origin",
1429
+ "place-content",
1430
+ "place-items",
1431
+ "place-self",
1432
+ "pointer-events",
1433
+ "position",
1434
+ "print-color-adjust",
1435
+ "quotes",
1436
+ "resize",
1437
+ "right",
1438
+ "rotate",
1439
+ "row-gap",
1440
+ "ruby-align",
1441
+ "ruby-merge",
1442
+ "scale",
1443
+ "scroll-behavior",
1444
+ "scroll-margin",
1445
+ "scroll-margin-block",
1446
+ "scroll-margin-block-end",
1447
+ "scroll-margin-block-start",
1448
+ "scroll-margin-bottom",
1449
+ "scroll-margin-inline",
1450
+ "scroll-margin-inline-end",
1451
+ "scroll-margin-inline-start",
1452
+ "scroll-margin-left",
1453
+ "scroll-margin-right",
1454
+ "scroll-margin-top",
1455
+ "scroll-padding",
1456
+ "scroll-padding-block",
1457
+ "scroll-padding-bottom",
1458
+ "scroll-padding-inline",
1459
+ "scroll-padding-left",
1460
+ "scroll-padding-right",
1461
+ "scroll-padding-top",
1462
+ "scroll-snap-align",
1463
+ "scroll-snap-stop",
1464
+ "scroll-snap-type",
1465
+ "scrollbar-color",
1466
+ "scrollbar-gutter",
1467
+ "scrollbar-width",
1468
+ "shape-image-threshold",
1469
+ "shape-margin",
1470
+ "shape-outside",
1471
+ "tab-size",
1472
+ "table-layout",
1473
+ "text-align",
1474
+ "text-align-last",
1475
+ "text-combine-upright",
1476
+ "text-decoration",
1477
+ "text-decoration-color",
1478
+ "text-decoration-line",
1479
+ "text-decoration-skip-ink",
1480
+ "text-decoration-style",
1481
+ "text-decoration-thickness",
1482
+ "text-emphasis",
1483
+ "text-emphasis-color",
1484
+ "text-emphasis-position",
1485
+ "text-emphasis-style",
1486
+ "text-indent",
1487
+ "text-justify",
1488
+ "text-orientation",
1489
+ "text-overflow",
1490
+ "text-rendering",
1491
+ "text-shadow",
1492
+ "text-transform",
1493
+ "text-underline-offset",
1494
+ "text-underline-position",
1495
+ "text-wrap",
1496
+ "top",
1497
+ "touch-action",
1498
+ "transform",
1499
+ "transform-box",
1500
+ "transform-origin",
1501
+ "transform-style",
1502
+ "transition",
1503
+ "transition-behavior",
1504
+ "transition-delay",
1505
+ "transition-duration",
1506
+ "transition-property",
1507
+ "transition-timing-function",
1508
+ "translate",
1509
+ "unicode-bidi",
1510
+ "user-select",
1511
+ "vertical-align",
1512
+ "visibility",
1513
+ "white-space",
1514
+ "white-space-collapse",
1515
+ "width",
1516
+ "will-change",
1517
+ "word-break",
1518
+ "word-spacing",
1519
+ "word-wrap",
1520
+ "writing-mode",
1521
+ "z-index",
1522
+ "zoom",
1523
+ "animation",
1524
+ "animation-composition",
1525
+ "animation-delay",
1526
+ "animation-direction",
1527
+ "animation-duration",
1528
+ "animation-fill-mode",
1529
+ "animation-iteration-count",
1530
+ "animation-name",
1531
+ "animation-play-state",
1532
+ "animation-range",
1533
+ "animation-timeline",
1534
+ "animation-timing-function",
1535
+ "view-transition-name",
1536
+ "interpolate-size",
1537
+ "text-box",
1538
+ "text-box-edge",
1539
+ "text-box-trim",
1540
+ "dynamic-range-limit",
1541
+ "field-sizing",
1542
+ "anchor-name",
1543
+ "position-anchor",
1544
+ "position-area",
1545
+ "scroll-timeline",
1546
+ "scroll-timeline-axis",
1547
+ "scroll-timeline-name",
1548
+ "timeline-scope",
1549
+ "view-timeline",
1550
+ "view-timeline-axis",
1551
+ "view-timeline-name",
1552
+ "-webkit-background-clip",
1553
+ "-webkit-text-fill-color",
1554
+ "-webkit-box-orient",
1555
+ "-webkit-line-clamp",
1556
+ "-webkit-tap-highlight-color",
1557
+ "-webkit-font-smoothing",
1558
+ "-moz-osx-font-smoothing",
1559
+ "-webkit-box-shadow",
1560
+ "-webkit-text-stroke",
1561
+ "-webkit-user-select",
1562
+ "-webkit-touch-callout",
1563
+ "-webkit-overflow-scrolling",
1564
+ "-webkit-appearance",
1565
+ "-moz-appearance",
1566
+ "-moz-binding",
1567
+ "-moz-border-bottom-colors",
1568
+ "-moz-border-left-colors",
1569
+ "-moz-border-right-colors",
1570
+ "-moz-border-top-colors",
1571
+ "-moz-context-properties",
1572
+ "-moz-float-edge",
1573
+ "-moz-force-broken-image-icon",
1574
+ "-moz-orient",
1575
+ "-moz-outline-radius",
1576
+ "-moz-outline-radius-bottomleft",
1577
+ "-moz-outline-radius-bottomright",
1578
+ "-moz-outline-radius-topleft",
1579
+ "-moz-outline-radius-topright",
1580
+ "-moz-stack-sizing",
1581
+ "-moz-text-blink",
1582
+ "-moz-user-focus",
1583
+ "-moz-user-input",
1584
+ "-moz-user-modify",
1585
+ "-moz-window-dragging",
1586
+ "-moz-window-shadow",
1587
+ "-ms-accelerator",
1588
+ "-ms-block-progression",
1589
+ "-ms-content-zoom-chaining",
1590
+ "-ms-content-zoom-limit",
1591
+ "-ms-content-zoom-limit-max",
1592
+ "-ms-content-zoom-limit-min",
1593
+ "-ms-content-zoom-snap",
1594
+ "-ms-content-zoom-snap-points",
1595
+ "-ms-content-zoom-snap-type",
1596
+ "-ms-content-zooming",
1597
+ "-ms-filter",
1598
+ "-ms-flow-from",
1599
+ "-ms-flow-into",
1600
+ "-ms-grid-columns",
1601
+ "-ms-grid-rows",
1602
+ "-ms-high-contrast-adjust",
1603
+ "-ms-hyphenate-limit-chars",
1604
+ "-ms-hyphenate-limit-lines",
1605
+ "-ms-hyphenate-limit-zone",
1606
+ "-ms-ime-align",
1607
+ "-ms-overflow-style",
1608
+ "-ms-scroll-chaining",
1609
+ "-ms-scroll-limit",
1610
+ "-ms-scroll-limit-x-max",
1611
+ "-ms-scroll-limit-x-min",
1612
+ "-ms-scroll-limit-y-max",
1613
+ "-ms-scroll-limit-y-min",
1614
+ "-ms-scroll-rails",
1615
+ "-ms-scroll-snap-points-x",
1616
+ "-ms-scroll-snap-points-y",
1617
+ "-ms-scroll-snap-type",
1618
+ "-ms-scroll-snap-x",
1619
+ "-ms-scroll-snap-y",
1620
+ "-ms-scroll-translation",
1621
+ "-ms-scrollbar-3dlight-color",
1622
+ "-ms-scrollbar-arrow-color",
1623
+ "-ms-scrollbar-base-color",
1624
+ "-ms-scrollbar-darkshadow-color",
1625
+ "-ms-scrollbar-face-color",
1626
+ "-ms-scrollbar-highlight-color",
1627
+ "-ms-scrollbar-shadow-color",
1628
+ "-ms-scrollbar-track-color",
1629
+ "-ms-text-autospace",
1630
+ "-ms-touch-select",
1631
+ "-ms-user-select",
1632
+ "-ms-wrap-flow",
1633
+ "-ms-wrap-margin",
1634
+ "-ms-wrap-through",
1635
+ "-webkit-border-after",
1636
+ "-webkit-border-after-color",
1637
+ "-webkit-border-after-style",
1638
+ "-webkit-border-after-width",
1639
+ "-webkit-border-before",
1640
+ "-webkit-border-before-color",
1641
+ "-webkit-border-before-style",
1642
+ "-webkit-border-before-width",
1643
+ "-webkit-border-end",
1644
+ "-webkit-border-end-color",
1645
+ "-webkit-border-end-style",
1646
+ "-webkit-border-end-width",
1647
+ "-webkit-border-start",
1648
+ "-webkit-border-start-color",
1649
+ "-webkit-border-start-style",
1650
+ "-webkit-border-start-width",
1651
+ "-webkit-box-reflect",
1652
+ "-webkit-mask",
1653
+ "-webkit-mask-attachment",
1654
+ "-webkit-mask-clip",
1655
+ "-webkit-mask-composite",
1656
+ "-webkit-mask-image",
1657
+ "-webkit-mask-origin",
1658
+ "-webkit-mask-position",
1659
+ "-webkit-mask-position-x",
1660
+ "-webkit-mask-position-y",
1661
+ "-webkit-mask-repeat",
1662
+ "-webkit-mask-repeat-x",
1663
+ "-webkit-mask-repeat-y",
1664
+ "-webkit-mask-size",
1665
+ "-webkit-text-stroke-color",
1666
+ "-webkit-text-stroke-width",
1667
+ "-webkit-user-modify",
1668
+ "alignment-baseline",
1669
+ "anchor-scope",
1670
+ "animation-range-end",
1671
+ "animation-range-start",
1672
+ "animation-trigger",
1673
+ "background-position-x",
1674
+ "background-position-y",
1675
+ "baseline-shift",
1676
+ "baseline-source",
1677
+ "border-shape",
1678
+ "box-align",
1679
+ "box-direction",
1680
+ "box-flex",
1681
+ "box-flex-group",
1682
+ "box-lines",
1683
+ "box-ordinal-group",
1684
+ "box-orient",
1685
+ "box-pack",
1686
+ "caption-side",
1687
+ "caret-animation",
1688
+ "clip-rule",
1689
+ "color-interpolation-filters",
1690
+ "column-height",
1691
+ "column-wrap",
1692
+ "contain-intrinsic-block-size",
1693
+ "contain-intrinsic-height",
1694
+ "contain-intrinsic-inline-size",
1695
+ "contain-intrinsic-width",
1696
+ "corner-block-end-shape",
1697
+ "corner-block-start-shape",
1698
+ "corner-bottom-left-shape",
1699
+ "corner-bottom-right-shape",
1700
+ "corner-bottom-shape",
1701
+ "corner-end-end-shape",
1702
+ "corner-end-start-shape",
1703
+ "corner-inline-end-shape",
1704
+ "corner-inline-start-shape",
1705
+ "corner-left-shape",
1706
+ "corner-right-shape",
1707
+ "corner-shape",
1708
+ "corner-start-end-shape",
1709
+ "corner-start-start-shape",
1710
+ "corner-top-left-shape",
1711
+ "corner-top-right-shape",
1712
+ "corner-top-shape",
1713
+ "cx",
1714
+ "cy",
1715
+ "d",
1716
+ "direction",
1717
+ "dominant-baseline",
1718
+ "flex-line-count",
1719
+ "flood-color",
1720
+ "flood-opacity",
1721
+ "font-smooth",
1722
+ "font-synthesis-position",
1723
+ "font-synthesis-small-caps",
1724
+ "font-synthesis-style",
1725
+ "font-synthesis-weight",
1726
+ "font-variant-alternates",
1727
+ "font-variant-emoji",
1728
+ "font-width",
1729
+ "frame-sizing",
1730
+ "image-resolution",
1731
+ "initial-letter",
1732
+ "initial-letter-align",
1733
+ "interactivity",
1734
+ "interest-delay",
1735
+ "interest-delay-end",
1736
+ "interest-delay-start",
1737
+ "lighting-color",
1738
+ "line-clamp",
1739
+ "line-height-step",
1740
+ "link-parameters",
1741
+ "marker",
1742
+ "marker-end",
1743
+ "marker-mid",
1744
+ "marker-start",
1745
+ "mask-type",
1746
+ "masonry-auto-flow",
1747
+ "math-depth",
1748
+ "math-shift",
1749
+ "math-style",
1750
+ "max-block-size",
1751
+ "max-lines",
1752
+ "object-view-box",
1753
+ "offset-position",
1754
+ "orphans",
1755
+ "overflow-block",
1756
+ "overflow-clip-box",
1757
+ "overflow-inline",
1758
+ "overlay",
1759
+ "page",
1760
+ "path-length",
1761
+ "position-try",
1762
+ "position-try-fallbacks",
1763
+ "position-try-order",
1764
+ "position-visibility",
1765
+ "r",
1766
+ "reading-flow",
1767
+ "reading-order",
1768
+ "ruby-overhang",
1769
+ "ruby-position",
1770
+ "rx",
1771
+ "ry",
1772
+ "scroll-axis-lock",
1773
+ "scroll-initial-target",
1774
+ "scroll-marker-group",
1775
+ "scroll-padding-block-end",
1776
+ "scroll-padding-block-start",
1777
+ "scroll-padding-inline-end",
1778
+ "scroll-padding-inline-start",
1779
+ "scroll-target-group",
1780
+ "shape-rendering",
1781
+ "speak-as",
1782
+ "stop-color",
1783
+ "stop-opacity",
1784
+ "stroke-color",
1785
+ "stroke-miterlimit",
1786
+ "text-anchor",
1787
+ "text-autospace",
1788
+ "text-decoration-inset",
1789
+ "text-decoration-skip",
1790
+ "text-fit",
1791
+ "text-size-adjust",
1792
+ "text-spacing-trim",
1793
+ "text-wrap-mode",
1794
+ "text-wrap-style",
1795
+ "timeline-trigger",
1796
+ "timeline-trigger-activation-range",
1797
+ "timeline-trigger-activation-range-end",
1798
+ "timeline-trigger-activation-range-start",
1799
+ "timeline-trigger-active-range",
1800
+ "timeline-trigger-active-range-end",
1801
+ "timeline-trigger-active-range-start",
1802
+ "timeline-trigger-name",
1803
+ "timeline-trigger-source",
1804
+ "trigger-scope",
1805
+ "vector-effect",
1806
+ "view-timeline-inset",
1807
+ "view-transition-class",
1808
+ "view-transition-scope",
1809
+ "widows",
1810
+ "window-drag",
1811
+ "x",
1812
+ "y"
1813
+ ],
1814
+ "unitlessProps": [
1815
+ "opacity",
1816
+ "line-height",
1817
+ "z-index",
1818
+ "font-weight",
1819
+ "flex-grow",
1820
+ "flex-shrink",
1821
+ "flex",
1822
+ "order",
1823
+ "scale",
1824
+ "widows",
1825
+ "orphans",
1826
+ "column-count",
1827
+ "aspect-ratio",
1828
+ "animation-iteration-count",
1829
+ "flood-opacity",
1830
+ "fill-opacity",
1831
+ "stop-opacity",
1832
+ "shape-image-threshold",
1833
+ "counter-reset",
1834
+ "counter-increment",
1835
+ "counter-set",
1836
+ "grid-row",
1837
+ "grid-column",
1838
+ "grid-area",
1839
+ "font-feature-settings",
1840
+ "font-variation-settings",
1841
+ "tab-size",
1842
+ "-webkit-line-clamp",
1843
+ "line-clamp",
1844
+ "stroke-miterlimit",
1845
+ "stroke-opacity",
1846
+ "initial-letter",
1847
+ "font-size-adjust"
1848
+ ],
1849
+ "timeProps": [
1850
+ "transition-delay",
1851
+ "transition-duration",
1852
+ "animation-delay",
1853
+ "animation-duration",
1854
+ "animation"
1855
+ ],
1856
+ "unitlessFns": [
1857
+ "oklch",
1858
+ "oklab",
1859
+ "lab",
1860
+ "lch",
1861
+ "hsl",
1862
+ "hsla",
1863
+ "rgb",
1864
+ "rgba",
1865
+ "hwb",
1866
+ "color",
1867
+ "color-mix",
1868
+ "light-dark",
1869
+ "brightness",
1870
+ "saturate",
1871
+ "contrast",
1872
+ "grayscale",
1873
+ "sepia",
1874
+ "invert",
1875
+ "opacity",
1876
+ "scale",
1877
+ "scalex",
1878
+ "scaley",
1879
+ "scalez",
1880
+ "scale3d",
1881
+ "steps",
1882
+ "cubic-bezier",
1883
+ "linear",
1884
+ "radial",
1885
+ "conic"
1886
+ ],
1887
+ "lengthFns": [
1888
+ "calc",
1889
+ "min",
1890
+ "max",
1891
+ "clamp",
1892
+ "round",
1893
+ "mod",
1894
+ "rem",
1895
+ "abs",
1896
+ "sign",
1897
+ "hypot",
1898
+ "pow",
1899
+ "sqrt",
1900
+ "log",
1901
+ "exp",
1902
+ "blur",
1903
+ "drop-shadow"
1904
+ ],
1905
+ "colorWords": [
1906
+ "black",
1907
+ "silver",
1908
+ "gray",
1909
+ "white",
1910
+ "maroon",
1911
+ "red",
1912
+ "purple",
1913
+ "fuchsia",
1914
+ "green",
1915
+ "lime",
1916
+ "olive",
1917
+ "yellow",
1918
+ "navy",
1919
+ "blue",
1920
+ "teal",
1921
+ "aqua",
1922
+ "orange",
1923
+ "gold",
1924
+ "pink",
1925
+ "brown",
1926
+ "violet",
1927
+ "cyan",
1928
+ "magenta",
1929
+ "indigo",
1930
+ "crimson",
1931
+ "coral",
1932
+ "tomato",
1933
+ "goldenrod",
1934
+ "khaki",
1935
+ "slategray",
1936
+ "slategrey",
1937
+ "dimgray",
1938
+ "dimgrey",
1939
+ "lightgray",
1940
+ "lightgrey",
1941
+ "darkgray",
1942
+ "darkgrey",
1943
+ "gainsboro",
1944
+ "whitesmoke",
1945
+ "ghostwhite",
1946
+ "ivory",
1947
+ "beige",
1948
+ "linen",
1949
+ "seashell",
1950
+ "snow",
1951
+ "honeydew",
1952
+ "mintcream",
1953
+ "azure",
1954
+ "aliceblue",
1955
+ "lavender",
1956
+ "canvas",
1957
+ "canvastext",
1958
+ "linktext",
1959
+ "visitedtext",
1960
+ "activetext",
1961
+ "buttonface",
1962
+ "buttontext",
1963
+ "buttonborder",
1964
+ "field",
1965
+ "fieldtext",
1966
+ "highlight",
1967
+ "highlighttext",
1968
+ "currentcolor",
1969
+ "transparent"
1970
+ ],
1971
+ "fluidProps": [
1972
+ "font-size",
1973
+ "width",
1974
+ "height",
1975
+ "inline-size",
1976
+ "block-size",
1977
+ "min-width",
1978
+ "max-width",
1979
+ "min-height",
1980
+ "max-height",
1981
+ "gap",
1982
+ "row-gap",
1983
+ "column-gap",
1984
+ "padding",
1985
+ "margin",
1986
+ "inset",
1987
+ "top",
1988
+ "right",
1989
+ "bottom",
1990
+ "left",
1991
+ "padding-top",
1992
+ "padding-right",
1993
+ "padding-bottom",
1994
+ "padding-left",
1995
+ "padding-inline",
1996
+ "padding-block",
1997
+ "margin-top",
1998
+ "margin-right",
1999
+ "margin-bottom",
2000
+ "margin-left",
2001
+ "margin-inline",
2002
+ "margin-block",
2003
+ "letter-spacing",
2004
+ "border-radius"
2005
+ ],
2006
+ "placeWords": {
2007
+ "center": [
2008
+ "center",
2009
+ "center"
2010
+ ],
2011
+ "start": [
2012
+ "start",
2013
+ "flex-start"
2014
+ ],
2015
+ "end": [
2016
+ "end",
2017
+ "flex-end"
2018
+ ],
2019
+ "between": [
2020
+ "center",
2021
+ "space-between"
2022
+ ],
2023
+ "around": [
2024
+ "center",
2025
+ "space-around"
2026
+ ],
2027
+ "evenly": [
2028
+ "center",
2029
+ "space-evenly"
2030
+ ],
2031
+ "stretch": [
2032
+ "stretch",
2033
+ "flex-start"
2034
+ ],
2035
+ "baseline": [
2036
+ "baseline",
2037
+ "flex-start"
2038
+ ]
2039
+ },
2040
+ "easings": {
2041
+ "snap": "cubic-bezier(0.2, 0.9, 0.3, 1)",
2042
+ "smooth": "cubic-bezier(0.4, 0, 0.2, 1)",
2043
+ "bounce": "cubic-bezier(0.34, 1.56, 0.64, 1)",
2044
+ "linear": "linear",
2045
+ "ease": "ease",
2046
+ "ease-in": "ease-in",
2047
+ "ease-out": "ease-out",
2048
+ "ease-in-out": "ease-in-out"
2049
+ },
2050
+ "defaultTokens": {
2051
+ "color.ink": "#16161d",
2052
+ "color.paper": "#faf7f2",
2053
+ "color.card": "#ffffff",
2054
+ "color.muted": "#6f6a7a",
2055
+ "color.line": "#e5ded2",
2056
+ "color.accent": "oklch(62% 0.19 35)",
2057
+ "color.accent-ink": "#fff6ee",
2058
+ "color.ok": "#2e7d4f",
2059
+ "color.err": "#b3372e",
2060
+ "space.1": "4",
2061
+ "space.2": "8",
2062
+ "space.3": "12",
2063
+ "space.4": "16",
2064
+ "space.5": "24",
2065
+ "space.6": "32",
2066
+ "space.7": "48",
2067
+ "space.8": "64",
2068
+ "radius.sm": "8",
2069
+ "radius.md": "14",
2070
+ "radius.lg": "22",
2071
+ "radius.full": "999",
2072
+ "shadow.sm": "0 1 2 color.ink/8, 0 1 3 color.ink/6",
2073
+ "shadow.md": "0 2 4 color.ink/10, 0 4 12 color.ink/8",
2074
+ "shadow.lg": "0 4 8 color.ink/12, 0 12 32 color.ink/12",
2075
+ "shadow.xl": "0 8 16 color.ink/14, 0 24 64 color.ink/16",
2076
+ "font.sans": "system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif",
2077
+ "font.mono": "ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace"
2078
+ },
2079
+ "semanticNames": [
2080
+ "size",
2081
+ "row",
2082
+ "column",
2083
+ "grid",
2084
+ "stack",
2085
+ "place",
2086
+ "center",
2087
+ "cover",
2088
+ "sticky",
2089
+ "font",
2090
+ "fluid",
2091
+ "shadow",
2092
+ "radius",
2093
+ "circle",
2094
+ "align",
2095
+ "z",
2096
+ "grow",
2097
+ "shrink",
2098
+ "basis",
2099
+ "truncate",
2100
+ "visually-hidden",
2101
+ "transition",
2102
+ "animate",
2103
+ "stagger",
2104
+ "overlap",
2105
+ "bend",
2106
+ "bend-y",
2107
+ "bend-up",
2108
+ "fold",
2109
+ "bend-around",
2110
+ "stretch",
2111
+ "gradient",
2112
+ "text-stroke",
2113
+ "yield",
2114
+ "outline-shape",
2115
+ "rotate",
2116
+ "gradient-text",
2117
+ "gradient-flow",
2118
+ "texture",
2119
+ "aura",
2120
+ "aura-glow",
2121
+ "aura-particle",
2122
+ "aura-particles",
2123
+ "glow-particle",
2124
+ "glow-particles",
2125
+ "aura-glow-particle",
2126
+ "aura-glow-particles",
2127
+ "glass",
2128
+ "tilt",
2129
+ "slider"
2130
+ ],
2131
+ "namedColors": {
2132
+ "aliceblue": "#f0f8ff",
2133
+ "antiquewhite": "#faebd7",
2134
+ "aqua": "#00ffff",
2135
+ "aquamarine": "#7fffd4",
2136
+ "azure": "#f0ffff",
2137
+ "beige": "#f5f5dc",
2138
+ "bisque": "#ffe4c4",
2139
+ "black": "#000000",
2140
+ "blanchedalmond": "#ffebcd",
2141
+ "blue": "#0000ff",
2142
+ "blueviolet": "#8a2be2",
2143
+ "brown": "#a52a2a",
2144
+ "burlywood": "#deb887",
2145
+ "cadetblue": "#5f9ea0",
2146
+ "chartreuse": "#7fff00",
2147
+ "chocolate": "#d2691e",
2148
+ "coral": "#ff7f50",
2149
+ "cornflowerblue": "#6495ed",
2150
+ "cornsilk": "#fff8dc",
2151
+ "crimson": "#dc143c",
2152
+ "cyan": "#00ffff",
2153
+ "darkblue": "#00008b",
2154
+ "darkcyan": "#008b8b",
2155
+ "darkgoldenrod": "#b8860b",
2156
+ "darkgray": "#a9a9a9",
2157
+ "darkgreen": "#006400",
2158
+ "darkgrey": "#a9a9a9",
2159
+ "darkkhaki": "#bdb76b",
2160
+ "darkmagenta": "#8b008b",
2161
+ "darkolivegreen": "#556b2f",
2162
+ "darkorange": "#ff8c00",
2163
+ "darkorchid": "#9932cc",
2164
+ "darkred": "#8b0000",
2165
+ "darksalmon": "#e9967a",
2166
+ "darkseagreen": "#8fbc8f",
2167
+ "darkslateblue": "#483d8b",
2168
+ "darkslategray": "#2f4f4f",
2169
+ "darkslategrey": "#2f4f4f",
2170
+ "darkturquoise": "#00ced1",
2171
+ "darkviolet": "#9400d3",
2172
+ "deeppink": "#ff1493",
2173
+ "deepskyblue": "#00bfff",
2174
+ "dimgray": "#696969",
2175
+ "dimgrey": "#696969",
2176
+ "dodgerblue": "#1e90ff",
2177
+ "firebrick": "#b22222",
2178
+ "floralwhite": "#fffaf0",
2179
+ "forestgreen": "#228b22",
2180
+ "fuchsia": "#ff00ff",
2181
+ "gainsboro": "#dcdcdc",
2182
+ "ghostwhite": "#f8f8ff",
2183
+ "gold": "#ffd700",
2184
+ "goldenrod": "#daa520",
2185
+ "gray": "#808080",
2186
+ "green": "#008000",
2187
+ "greenyellow": "#adff2f",
2188
+ "grey": "#808080",
2189
+ "honeydew": "#f0fff0",
2190
+ "hotpink": "#ff69b4",
2191
+ "indianred": "#cd5c5c",
2192
+ "indigo": "#4b0082",
2193
+ "ivory": "#fffff0",
2194
+ "khaki": "#f0e68c",
2195
+ "lavender": "#e6e6fa",
2196
+ "lavenderblush": "#fff0f5",
2197
+ "lawngreen": "#7cfc00",
2198
+ "lemonchiffon": "#fffacd",
2199
+ "lightblue": "#add8e6",
2200
+ "lightcoral": "#f08080",
2201
+ "lightcyan": "#e0ffff",
2202
+ "lightgoldenrodyellow": "#fafad2",
2203
+ "lightgray": "#d3d3d3",
2204
+ "lightgreen": "#90ee90",
2205
+ "lightgrey": "#d3d3d3",
2206
+ "lightpink": "#ffb6c1",
2207
+ "lightsalmon": "#ffa07a",
2208
+ "lightseagreen": "#20b2aa",
2209
+ "lightskyblue": "#87cefa",
2210
+ "lightslategray": "#778899",
2211
+ "lightslategrey": "#778899",
2212
+ "lightsteelblue": "#b0c4de",
2213
+ "lightyellow": "#ffffe0",
2214
+ "lime": "#00ff00",
2215
+ "limegreen": "#32cd32",
2216
+ "linen": "#faf0e6",
2217
+ "magenta": "#ff00ff",
2218
+ "maroon": "#800000",
2219
+ "mediumaquamarine": "#66cdaa",
2220
+ "mediumblue": "#0000cd",
2221
+ "mediumorchid": "#ba55d3",
2222
+ "mediumpurple": "#9370db",
2223
+ "mediumseagreen": "#3cb371",
2224
+ "mediumslateblue": "#7b68ee",
2225
+ "mediumspringgreen": "#00fa9a",
2226
+ "mediumturquoise": "#48d1cc",
2227
+ "mediumvioletred": "#c71585",
2228
+ "midnightblue": "#191970",
2229
+ "mintcream": "#f5fffa",
2230
+ "mistyrose": "#ffe4e1",
2231
+ "moccasin": "#ffe4b5",
2232
+ "navajowhite": "#ffdead",
2233
+ "navy": "#000080",
2234
+ "oldlace": "#fdf5e6",
2235
+ "olive": "#808000",
2236
+ "olivedrab": "#6b8e23",
2237
+ "orange": "#ffa500",
2238
+ "orangered": "#ff4500",
2239
+ "orchid": "#da70d6",
2240
+ "palegoldenrod": "#eee8aa",
2241
+ "palegreen": "#98fb98",
2242
+ "paleturquoise": "#afeeee",
2243
+ "palevioletred": "#db7093",
2244
+ "papayawhip": "#ffefd5",
2245
+ "peachpuff": "#ffdab9",
2246
+ "peru": "#cd853f",
2247
+ "pink": "#ffc0cb",
2248
+ "plum": "#dda0dd",
2249
+ "powderblue": "#b0e0e6",
2250
+ "purple": "#800080",
2251
+ "rebeccapurple": "#663399",
2252
+ "red": "#ff0000",
2253
+ "rosybrown": "#bc8f8f",
2254
+ "royalblue": "#4169e1",
2255
+ "saddlebrown": "#8b4513",
2256
+ "salmon": "#fa8072",
2257
+ "sandybrown": "#f4a460",
2258
+ "seagreen": "#2e8b57",
2259
+ "seashell": "#fff5ee",
2260
+ "sienna": "#a0522d",
2261
+ "silver": "#c0c0c0",
2262
+ "skyblue": "#87ceeb",
2263
+ "slateblue": "#6a5acd",
2264
+ "slategray": "#708090",
2265
+ "slategrey": "#708090",
2266
+ "snow": "#fffafa",
2267
+ "springgreen": "#00ff7f",
2268
+ "steelblue": "#4682b4",
2269
+ "tan": "#d2b48c",
2270
+ "teal": "#008080",
2271
+ "thistle": "#d8bfd8",
2272
+ "tomato": "#ff6347",
2273
+ "turquoise": "#40e0d0",
2274
+ "violet": "#ee82ee",
2275
+ "wheat": "#f5deb3",
2276
+ "white": "#ffffff",
2277
+ "whitesmoke": "#f5f5f5",
2278
+ "yellow": "#ffff00",
2279
+ "yellowgreen": "#9acd32"
2280
+ },
2281
+ "texture": {
2282
+ "generators": [
2283
+ "noise",
2284
+ "bump",
2285
+ "lines",
2286
+ "dots",
2287
+ "grid",
2288
+ "checker",
2289
+ "weave",
2290
+ "carbon",
2291
+ "waves",
2292
+ "hex",
2293
+ "rings",
2294
+ "bricks",
2295
+ "herringbone",
2296
+ "plaid",
2297
+ "scales",
2298
+ "chevron",
2299
+ "diamonds",
2300
+ "triangles",
2301
+ "starfield",
2302
+ "terrazzo"
2303
+ ],
2304
+ "blendModes": [
2305
+ "normal",
2306
+ "multiply",
2307
+ "screen",
2308
+ "overlay",
2309
+ "soft-light",
2310
+ "hard-light",
2311
+ "color-burn",
2312
+ "color-dodge",
2313
+ "darken",
2314
+ "lighten",
2315
+ "difference",
2316
+ "exclusion"
2317
+ ],
2318
+ "keys": {
2319
+ "scale": {
2320
+ "words": 1,
2321
+ "kind": "number",
2322
+ "min": 0.1,
2323
+ "max": 2000,
2324
+ "doc": "feature size in px: how big the grains, cells or lines are"
2325
+ },
2326
+ "intensity": {
2327
+ "words": 1,
2328
+ "kind": "number",
2329
+ "min": 0,
2330
+ "max": 2,
2331
+ "doc": "how strong the layer is, 0 to 1 (more than 1 over-drives it)"
2332
+ },
2333
+ "contrast": {
2334
+ "words": 1,
2335
+ "kind": "number",
2336
+ "min": 0,
2337
+ "max": 40,
2338
+ "doc": "noise contrast: 1 is as generated, higher is harsher"
2339
+ },
2340
+ "threshold": {
2341
+ "words": 1,
2342
+ "kind": "number",
2343
+ "min": 0,
2344
+ "max": 1,
2345
+ "doc": "noise cut-off: only values above it show (glitter uses a high one)"
2346
+ },
2347
+ "contour": {
2348
+ "words": 1,
2349
+ "kind": "number",
2350
+ "min": 0.005,
2351
+ "max": 0.5,
2352
+ "doc": "draw thin lines where the noise crosses the threshold, instead of a plain cut: veins, rings, contours"
2353
+ },
2354
+ "octaves": {
2355
+ "words": 1,
2356
+ "kind": "number",
2357
+ "min": 1,
2358
+ "max": 8,
2359
+ "int": true,
2360
+ "doc": "noise detail layers, 1 is smooth, 5 is rough"
2361
+ },
2362
+ "seed": {
2363
+ "words": 1,
2364
+ "kind": "number",
2365
+ "min": 0,
2366
+ "max": 99999,
2367
+ "int": true,
2368
+ "doc": "a different random pattern with the same look"
2369
+ },
2370
+ "stretch": {
2371
+ "words": 2,
2372
+ "kind": "numbers",
2373
+ "min": 0.1,
2374
+ "max": 200,
2375
+ "doc": "stretch noise features along x and y, e.g. stretch 40 1 for wood grain"
2376
+ },
2377
+ "depth": {
2378
+ "words": 1,
2379
+ "kind": "number",
2380
+ "min": 0,
2381
+ "max": 30,
2382
+ "doc": "bump height: how deep the relief looks"
2383
+ },
2384
+ "light": {
2385
+ "words": 1,
2386
+ "kind": "number",
2387
+ "min": -360,
2388
+ "max": 360,
2389
+ "doc": "bump light direction in degrees"
2390
+ },
2391
+ "angle": {
2392
+ "words": 1,
2393
+ "kind": "number",
2394
+ "min": -360,
2395
+ "max": 360,
2396
+ "doc": "direction of lines and patterns, in degrees"
2397
+ },
2398
+ "thickness": {
2399
+ "words": 1,
2400
+ "kind": "number",
2401
+ "min": 0.1,
2402
+ "max": 500,
2403
+ "doc": "line or grid-line width in px"
2404
+ },
2405
+ "radius": {
2406
+ "words": 1,
2407
+ "kind": "number",
2408
+ "min": 0.02,
2409
+ "max": 1,
2410
+ "doc": "dot size as a fraction of the cell (plaid: how wide the bands are as a fraction of the repeat)"
2411
+ },
2412
+ "softness": {
2413
+ "words": 1,
2414
+ "kind": "number",
2415
+ "min": 0,
2416
+ "max": 1,
2417
+ "doc": "how soft the edges of dots and lines are"
2418
+ },
2419
+ "animate": {
2420
+ "words": 1,
2421
+ "kind": "number",
2422
+ "min": 0,
2423
+ "max": 60000,
2424
+ "doc": "milliseconds per shimmer step (film-grain flicker); 0 is still"
2425
+ },
2426
+ "type": {
2427
+ "words": 1,
2428
+ "kind": "word",
2429
+ "values": [
2430
+ "fractal",
2431
+ "turbulence"
2432
+ ],
2433
+ "doc": "noise flavour: fractal is clouds, turbulence is veins"
2434
+ },
2435
+ "blend": {
2436
+ "words": 1,
2437
+ "kind": "word",
2438
+ "values": [
2439
+ "normal",
2440
+ "multiply",
2441
+ "screen",
2442
+ "overlay",
2443
+ "soft-light",
2444
+ "hard-light",
2445
+ "color-burn",
2446
+ "color-dodge",
2447
+ "darken",
2448
+ "lighten",
2449
+ "difference",
2450
+ "exclusion"
2451
+ ],
2452
+ "doc": "how the layer mixes with what is under it"
2453
+ },
2454
+ "color": {
2455
+ "words": 1,
2456
+ "kind": "color",
2457
+ "doc": "the main colour (dark end of noise, ink of lines and dots)"
2458
+ },
2459
+ "color2": {
2460
+ "words": 1,
2461
+ "kind": "color",
2462
+ "doc": "the second colour (light end of noise, background cell of patterns)"
2463
+ },
2464
+ "figure": {
2465
+ "words": 1,
2466
+ "kind": "number",
2467
+ "min": 0,
2468
+ "max": 1,
2469
+ "doc": "rings generator: how strongly the growth rings arch into a cathedral figure, 0 is straight grain"
2470
+ },
2471
+ "warp": {
2472
+ "words": 1,
2473
+ "kind": "number",
2474
+ "min": 0,
2475
+ "max": 1,
2476
+ "doc": "rings generator: how much each ring wobbles along its length"
2477
+ },
2478
+ "pores": {
2479
+ "words": 1,
2480
+ "kind": "number",
2481
+ "min": 0,
2482
+ "max": 1,
2483
+ "doc": "rings generator: fine fibre and pore streaks between the rings"
2484
+ },
2485
+ "knots": {
2486
+ "words": 1,
2487
+ "kind": "number",
2488
+ "min": 0,
2489
+ "max": 8,
2490
+ "int": true,
2491
+ "doc": "rings generator: how many knots in one tile (the pattern repeats, so keep it small)"
2492
+ },
2493
+ "planks": {
2494
+ "words": 1,
2495
+ "kind": "number",
2496
+ "min": 24,
2497
+ "max": 1200,
2498
+ "doc": "rings generator: lay it out as planks this many px tall, with a seam and a slightly different board each"
2499
+ },
2500
+ "symmetrical": {
2501
+ "words": 1,
2502
+ "kind": "word",
2503
+ "values": [
2504
+ "both",
2505
+ "x",
2506
+ "y"
2507
+ ],
2508
+ "optional": true,
2509
+ "doc": "mirror every other tile, so each edge meets its own reflection: no seam, and the repeat is twice as far apart. x mirrors left to right, y top to bottom, both (the default) does both. Works on the image-based textures (noise, bump, rings, waves, hex)."
2510
+ },
2511
+ "density": {
2512
+ "words": 1,
2513
+ "kind": "number",
2514
+ "min": 0.05,
2515
+ "max": 20,
2516
+ "doc": "starfield and terrazzo: how many stars or chips in a tile, 1 is normal"
2517
+ },
2518
+ "invert": {
2519
+ "words": 0,
2520
+ "kind": "flag",
2521
+ "doc": "swap the two colours"
2522
+ }
2523
+ },
2524
+ "presetNames": [
2525
+ "grain",
2526
+ "noise",
2527
+ "rough",
2528
+ "sand",
2529
+ "paper",
2530
+ "cloud",
2531
+ "concrete",
2532
+ "stone",
2533
+ "crumpled",
2534
+ "glitter",
2535
+ "frost",
2536
+ "marble",
2537
+ "wood",
2538
+ "oak",
2539
+ "walnut",
2540
+ "cherry",
2541
+ "maple",
2542
+ "pine",
2543
+ "mahogany",
2544
+ "ebony",
2545
+ "birch",
2546
+ "oak-planks",
2547
+ "walnut-planks",
2548
+ "bark",
2549
+ "leather",
2550
+ "brushed",
2551
+ "canvas",
2552
+ "linen",
2553
+ "denim",
2554
+ "carbon",
2555
+ "scanlines",
2556
+ "halftone",
2557
+ "dots",
2558
+ "lines",
2559
+ "grid",
2560
+ "checker",
2561
+ "crosshatch",
2562
+ "stripes",
2563
+ "waves",
2564
+ "hex",
2565
+ "bricks",
2566
+ "bricks-old",
2567
+ "herringbone",
2568
+ "plaid",
2569
+ "tartan",
2570
+ "gingham",
2571
+ "scales",
2572
+ "mermaid",
2573
+ "shingles",
2574
+ "chevron",
2575
+ "argyle",
2576
+ "lowpoly",
2577
+ "tiles",
2578
+ "blueprint",
2579
+ "granite",
2580
+ "slate",
2581
+ "sandstone",
2582
+ "terrazzo",
2583
+ "confetti",
2584
+ "rust",
2585
+ "corrugated",
2586
+ "cork",
2587
+ "kraft",
2588
+ "felt",
2589
+ "burlap",
2590
+ "stucco",
2591
+ "starfield",
2592
+ "snow",
2593
+ "grass",
2594
+ "water",
2595
+ "rain",
2596
+ "lava",
2597
+ "topo"
2598
+ ],
2599
+ "presets": {
2600
+ "grain": {
2601
+ "doc": "fine film grain, neutral on any colour",
2602
+ "mask": true,
2603
+ "layers": [
2604
+ {
2605
+ "gen": "noise",
2606
+ "scale": 1.1,
2607
+ "octaves": 2,
2608
+ "contrast": 1.7,
2609
+ "intensity": 0.5,
2610
+ "color": "#000",
2611
+ "color2": "#fff",
2612
+ "blend": "overlay"
2613
+ }
2614
+ ]
2615
+ },
2616
+ "noise": {
2617
+ "doc": "coarser static",
2618
+ "mask": true,
2619
+ "layers": [
2620
+ {
2621
+ "gen": "noise",
2622
+ "scale": 2.6,
2623
+ "octaves": 3,
2624
+ "contrast": 1.9,
2625
+ "intensity": 0.5,
2626
+ "color": "#000",
2627
+ "color2": "#fff",
2628
+ "blend": "overlay"
2629
+ }
2630
+ ]
2631
+ },
2632
+ "rough": {
2633
+ "doc": "gritty relief, like a rough cast wall",
2634
+ "mask": true,
2635
+ "layers": [
2636
+ {
2637
+ "gen": "bump",
2638
+ "scale": 3,
2639
+ "octaves": 4,
2640
+ "depth": 3.4,
2641
+ "light": 135,
2642
+ "intensity": 0.75,
2643
+ "color": "#000",
2644
+ "color2": "#fff",
2645
+ "blend": "overlay"
2646
+ }
2647
+ ]
2648
+ },
2649
+ "sand": {
2650
+ "doc": "warm fine sand grains",
2651
+ "mask": true,
2652
+ "layers": [
2653
+ {
2654
+ "gen": "bump",
2655
+ "scale": 1.5,
2656
+ "octaves": 3,
2657
+ "depth": 2.2,
2658
+ "light": 130,
2659
+ "intensity": 0.7,
2660
+ "color": "#5a4426",
2661
+ "color2": "#fff1d0",
2662
+ "blend": "overlay"
2663
+ }
2664
+ ]
2665
+ },
2666
+ "paper": {
2667
+ "doc": "soft paper fibre with a little unevenness",
2668
+ "layers": [
2669
+ {
2670
+ "gen": "noise",
2671
+ "scale": 1.5,
2672
+ "octaves": 4,
2673
+ "contrast": 1.2,
2674
+ "intensity": 0.3,
2675
+ "color": "#000",
2676
+ "color2": "#fff",
2677
+ "blend": "overlay"
2678
+ },
2679
+ {
2680
+ "gen": "noise",
2681
+ "scale": 120,
2682
+ "octaves": 3,
2683
+ "contrast": 1.3,
2684
+ "intensity": 0.25,
2685
+ "color": "#000",
2686
+ "color2": "#fff",
2687
+ "blend": "soft-light",
2688
+ "seed": 7
2689
+ }
2690
+ ]
2691
+ },
2692
+ "cloud": {
2693
+ "doc": "big soft cloudy variation",
2694
+ "mask": true,
2695
+ "layers": [
2696
+ {
2697
+ "gen": "noise",
2698
+ "scale": 90,
2699
+ "octaves": 5,
2700
+ "contrast": 1.6,
2701
+ "intensity": 0.7,
2702
+ "color": "#000",
2703
+ "color2": "#fff",
2704
+ "blend": "soft-light"
2705
+ }
2706
+ ]
2707
+ },
2708
+ "concrete": {
2709
+ "doc": "pitted concrete",
2710
+ "layers": [
2711
+ {
2712
+ "gen": "bump",
2713
+ "scale": 6,
2714
+ "octaves": 5,
2715
+ "depth": 2.6,
2716
+ "light": 135,
2717
+ "intensity": 0.7,
2718
+ "color": "#000",
2719
+ "color2": "#fff",
2720
+ "blend": "overlay"
2721
+ },
2722
+ {
2723
+ "gen": "noise",
2724
+ "scale": 140,
2725
+ "octaves": 4,
2726
+ "contrast": 1.5,
2727
+ "intensity": 0.45,
2728
+ "color": "#000",
2729
+ "color2": "#fff",
2730
+ "blend": "soft-light",
2731
+ "seed": 3
2732
+ }
2733
+ ]
2734
+ },
2735
+ "stone": {
2736
+ "doc": "carved stone with broad relief",
2737
+ "layers": [
2738
+ {
2739
+ "gen": "bump",
2740
+ "scale": 26,
2741
+ "octaves": 5,
2742
+ "depth": 2.4,
2743
+ "light": 125,
2744
+ "intensity": 0.75,
2745
+ "color": "#000",
2746
+ "color2": "#fff",
2747
+ "blend": "overlay"
2748
+ }
2749
+ ]
2750
+ },
2751
+ "crumpled": {
2752
+ "doc": "crumpled foil or paper, big creases",
2753
+ "layers": [
2754
+ {
2755
+ "gen": "bump",
2756
+ "scale": 34,
2757
+ "octaves": 4,
2758
+ "depth": 4,
2759
+ "light": 135,
2760
+ "intensity": 0.8,
2761
+ "color": "#000",
2762
+ "color2": "#fff",
2763
+ "blend": "overlay",
2764
+ "seed": 11
2765
+ }
2766
+ ]
2767
+ },
2768
+ "glitter": {
2769
+ "doc": "sparse bright sparkles",
2770
+ "mask": true,
2771
+ "layers": [
2772
+ {
2773
+ "gen": "noise",
2774
+ "scale": 1.2,
2775
+ "octaves": 1,
2776
+ "contrast": 16,
2777
+ "threshold": 0.66,
2778
+ "intensity": 1,
2779
+ "color": "rgba(255,255,255,0)",
2780
+ "color2": "#fff",
2781
+ "blend": "screen",
2782
+ "seed": 5
2783
+ }
2784
+ ]
2785
+ },
2786
+ "frost": {
2787
+ "doc": "fine icy crystals",
2788
+ "layers": [
2789
+ {
2790
+ "gen": "bump",
2791
+ "scale": 2.2,
2792
+ "octaves": 4,
2793
+ "depth": 3.2,
2794
+ "light": 140,
2795
+ "intensity": 0.95,
2796
+ "color": "rgba(160,210,255,0)",
2797
+ "color2": "#ffffff",
2798
+ "blend": "screen"
2799
+ }
2800
+ ]
2801
+ },
2802
+ "marble": {
2803
+ "doc": "veined marble",
2804
+ "layers": [
2805
+ {
2806
+ "gen": "noise",
2807
+ "scale": 120,
2808
+ "octaves": 5,
2809
+ "contour": 0.035,
2810
+ "threshold": 0.5,
2811
+ "intensity": 0.9,
2812
+ "color": "#6c6877",
2813
+ "color2": "rgba(247,245,240,0)",
2814
+ "blend": "multiply",
2815
+ "seed": 4
2816
+ },
2817
+ {
2818
+ "gen": "noise",
2819
+ "scale": 120,
2820
+ "octaves": 5,
2821
+ "contour": 0.16,
2822
+ "threshold": 0.5,
2823
+ "intensity": 0.25,
2824
+ "color": "#9b97a6",
2825
+ "color2": "rgba(247,245,240,0)",
2826
+ "blend": "multiply",
2827
+ "seed": 4
2828
+ },
2829
+ {
2830
+ "gen": "noise",
2831
+ "scale": 200,
2832
+ "octaves": 3,
2833
+ "contrast": 1.4,
2834
+ "intensity": 0.3,
2835
+ "color": "#cfc9c0",
2836
+ "color2": "#fbfaf7",
2837
+ "blend": "soft-light",
2838
+ "seed": 21
2839
+ }
2840
+ ]
2841
+ },
2842
+ "wood": {
2843
+ "doc": "long wood grain, along x (use angle 90 for vertical)",
2844
+ "layers": [
2845
+ {
2846
+ "gen": "noise",
2847
+ "scale": 4,
2848
+ "octaves": 3,
2849
+ "contrast": 1.8,
2850
+ "stretch": [
2851
+ 40,
2852
+ 1
2853
+ ],
2854
+ "intensity": 1,
2855
+ "color": "#7a4b24",
2856
+ "color2": "#c99a62",
2857
+ "blend": "multiply"
2858
+ }
2859
+ ]
2860
+ },
2861
+ "oak": {
2862
+ "doc": "oak: open grain, tan with brown rings",
2863
+ "layers": [
2864
+ {
2865
+ "gen": "rings",
2866
+ "scale": 11,
2867
+ "color": "#8a5f33",
2868
+ "color2": "#c9a574",
2869
+ "contrast": 1.3,
2870
+ "figure": 0.55,
2871
+ "warp": 0.45,
2872
+ "pores": 0.5,
2873
+ "seed": 3,
2874
+ "blend": "normal"
2875
+ }
2876
+ ]
2877
+ },
2878
+ "walnut": {
2879
+ "doc": "walnut: dark chocolate with black-brown rings",
2880
+ "layers": [
2881
+ {
2882
+ "gen": "rings",
2883
+ "scale": 9,
2884
+ "color": "#2b1a10",
2885
+ "color2": "#6a4631",
2886
+ "contrast": 1.2,
2887
+ "figure": 0.5,
2888
+ "warp": 0.55,
2889
+ "pores": 0.45,
2890
+ "seed": 8,
2891
+ "blend": "normal"
2892
+ }
2893
+ ]
2894
+ },
2895
+ "cherry": {
2896
+ "doc": "cherry: warm red-brown with fine rings",
2897
+ "layers": [
2898
+ {
2899
+ "gen": "rings",
2900
+ "scale": 8,
2901
+ "color": "#7a3a1c",
2902
+ "color2": "#bb7443",
2903
+ "contrast": 1.1,
2904
+ "figure": 0.4,
2905
+ "warp": 0.35,
2906
+ "pores": 0.35,
2907
+ "seed": 5,
2908
+ "blend": "normal"
2909
+ }
2910
+ ]
2911
+ },
2912
+ "maple": {
2913
+ "doc": "maple: pale, tight, nearly plain grain",
2914
+ "layers": [
2915
+ {
2916
+ "gen": "rings",
2917
+ "scale": 13,
2918
+ "color": "#c79a5a",
2919
+ "color2": "#ecd6ab",
2920
+ "contrast": 0.8,
2921
+ "figure": 0.25,
2922
+ "warp": 0.3,
2923
+ "pores": 0.25,
2924
+ "seed": 11,
2925
+ "blend": "normal"
2926
+ }
2927
+ ]
2928
+ },
2929
+ "pine": {
2930
+ "doc": "pine: light yellow wood with wide rings and knots",
2931
+ "layers": [
2932
+ {
2933
+ "gen": "rings",
2934
+ "scale": 16,
2935
+ "color": "#b5742f",
2936
+ "color2": "#e6c081",
2937
+ "contrast": 1.4,
2938
+ "figure": 0.65,
2939
+ "warp": 0.5,
2940
+ "pores": 0.4,
2941
+ "knots": 2,
2942
+ "seed": 14,
2943
+ "blend": "normal"
2944
+ }
2945
+ ]
2946
+ },
2947
+ "mahogany": {
2948
+ "doc": "mahogany: deep red-brown, straight grain",
2949
+ "layers": [
2950
+ {
2951
+ "gen": "rings",
2952
+ "scale": 10,
2953
+ "color": "#4a2012",
2954
+ "color2": "#8c4727",
2955
+ "contrast": 1.1,
2956
+ "figure": 0.3,
2957
+ "warp": 0.3,
2958
+ "pores": 0.4,
2959
+ "seed": 6,
2960
+ "blend": "normal"
2961
+ }
2962
+ ]
2963
+ },
2964
+ "ebony": {
2965
+ "doc": "ebony: nearly black with faint streaks",
2966
+ "layers": [
2967
+ {
2968
+ "gen": "rings",
2969
+ "scale": 12,
2970
+ "color": "#0c0908",
2971
+ "color2": "#2d2621",
2972
+ "contrast": 0.7,
2973
+ "figure": 0.2,
2974
+ "warp": 0.25,
2975
+ "pores": 0.3,
2976
+ "seed": 2,
2977
+ "blend": "normal"
2978
+ }
2979
+ ]
2980
+ },
2981
+ "birch": {
2982
+ "doc": "birch: creamy pale wood with fine rings",
2983
+ "layers": [
2984
+ {
2985
+ "gen": "rings",
2986
+ "scale": 10,
2987
+ "color": "#d2ae74",
2988
+ "color2": "#f0dfb9",
2989
+ "contrast": 0.9,
2990
+ "figure": 0.3,
2991
+ "warp": 0.35,
2992
+ "pores": 0.25,
2993
+ "seed": 9,
2994
+ "blend": "normal"
2995
+ }
2996
+ ]
2997
+ },
2998
+ "oak-planks": {
2999
+ "doc": "oak laid as planks, with seams",
3000
+ "layers": [
3001
+ {
3002
+ "gen": "rings",
3003
+ "scale": 11,
3004
+ "color": "#8a5f33",
3005
+ "color2": "#c9a574",
3006
+ "contrast": 1.3,
3007
+ "figure": 0.55,
3008
+ "warp": 0.45,
3009
+ "pores": 0.5,
3010
+ "planks": 110,
3011
+ "seed": 3,
3012
+ "blend": "normal"
3013
+ }
3014
+ ]
3015
+ },
3016
+ "walnut-planks": {
3017
+ "doc": "walnut laid as planks, with seams",
3018
+ "layers": [
3019
+ {
3020
+ "gen": "rings",
3021
+ "scale": 9,
3022
+ "color": "#2b1a10",
3023
+ "color2": "#6a4631",
3024
+ "contrast": 1.2,
3025
+ "figure": 0.5,
3026
+ "warp": 0.55,
3027
+ "pores": 0.45,
3028
+ "planks": 96,
3029
+ "seed": 8,
3030
+ "blend": "normal"
3031
+ }
3032
+ ]
3033
+ },
3034
+ "bark": {
3035
+ "doc": "rough vertical bark",
3036
+ "layers": [
3037
+ {
3038
+ "gen": "bump",
3039
+ "scale": 10,
3040
+ "octaves": 4,
3041
+ "depth": 5,
3042
+ "stretch": [
3043
+ 1,
3044
+ 6
3045
+ ],
3046
+ "light": 150,
3047
+ "intensity": 0.8,
3048
+ "color": "#000",
3049
+ "color2": "#fff",
3050
+ "blend": "overlay"
3051
+ }
3052
+ ]
3053
+ },
3054
+ "leather": {
3055
+ "doc": "pebbled leather",
3056
+ "layers": [
3057
+ {
3058
+ "gen": "bump",
3059
+ "scale": 5,
3060
+ "octaves": 2,
3061
+ "depth": 4.2,
3062
+ "light": 120,
3063
+ "intensity": 0.85,
3064
+ "color": "#000",
3065
+ "color2": "#fff",
3066
+ "blend": "overlay"
3067
+ }
3068
+ ]
3069
+ },
3070
+ "brushed": {
3071
+ "doc": "brushed metal, fine lines along x",
3072
+ "layers": [
3073
+ {
3074
+ "gen": "noise",
3075
+ "scale": 1.2,
3076
+ "octaves": 2,
3077
+ "contrast": 1.5,
3078
+ "stretch": [
3079
+ 60,
3080
+ 1
3081
+ ],
3082
+ "intensity": 0.5,
3083
+ "color": "#000",
3084
+ "color2": "#fff",
3085
+ "blend": "overlay"
3086
+ }
3087
+ ]
3088
+ },
3089
+ "canvas": {
3090
+ "doc": "artist canvas weave",
3091
+ "layers": [
3092
+ {
3093
+ "gen": "weave",
3094
+ "scale": 3,
3095
+ "thickness": 1,
3096
+ "intensity": 0.5,
3097
+ "color": "#000",
3098
+ "blend": "multiply"
3099
+ },
3100
+ {
3101
+ "gen": "noise",
3102
+ "scale": 1.4,
3103
+ "octaves": 3,
3104
+ "contrast": 1.3,
3105
+ "intensity": 0.3,
3106
+ "color": "#000",
3107
+ "color2": "#fff",
3108
+ "blend": "overlay"
3109
+ }
3110
+ ]
3111
+ },
3112
+ "linen": {
3113
+ "doc": "loose linen weave with slubs",
3114
+ "layers": [
3115
+ {
3116
+ "gen": "weave",
3117
+ "scale": 4,
3118
+ "thickness": 1.2,
3119
+ "intensity": 0.4,
3120
+ "color": "#5a4a32",
3121
+ "blend": "multiply"
3122
+ },
3123
+ {
3124
+ "gen": "noise",
3125
+ "scale": 80,
3126
+ "octaves": 3,
3127
+ "contrast": 1.4,
3128
+ "stretch": [
3129
+ 8,
3130
+ 1
3131
+ ],
3132
+ "intensity": 0.25,
3133
+ "color": "#000",
3134
+ "color2": "#fff",
3135
+ "blend": "soft-light",
3136
+ "seed": 9
3137
+ }
3138
+ ]
3139
+ },
3140
+ "denim": {
3141
+ "doc": "diagonal twill, blue by default",
3142
+ "layers": [
3143
+ {
3144
+ "gen": "lines",
3145
+ "angle": 45,
3146
+ "scale": 3,
3147
+ "thickness": 1.4,
3148
+ "intensity": 0.45,
3149
+ "color": "#0d2147",
3150
+ "blend": "multiply"
3151
+ },
3152
+ {
3153
+ "gen": "noise",
3154
+ "scale": 1.3,
3155
+ "octaves": 3,
3156
+ "contrast": 1.4,
3157
+ "intensity": 0.3,
3158
+ "color": "#000",
3159
+ "color2": "#fff",
3160
+ "blend": "overlay",
3161
+ "seed": 2
3162
+ }
3163
+ ]
3164
+ },
3165
+ "carbon": {
3166
+ "doc": "carbon fibre twill",
3167
+ "layers": [
3168
+ {
3169
+ "gen": "carbon",
3170
+ "scale": 20,
3171
+ "intensity": 1,
3172
+ "color": "#151515",
3173
+ "color2": "#262626",
3174
+ "blend": "normal"
3175
+ }
3176
+ ]
3177
+ },
3178
+ "scanlines": {
3179
+ "doc": "CRT scanlines",
3180
+ "layers": [
3181
+ {
3182
+ "gen": "lines",
3183
+ "angle": 0,
3184
+ "scale": 4,
3185
+ "thickness": 2,
3186
+ "intensity": 0.28,
3187
+ "color": "#000",
3188
+ "blend": "normal"
3189
+ }
3190
+ ]
3191
+ },
3192
+ "halftone": {
3193
+ "doc": "newsprint dots",
3194
+ "layers": [
3195
+ {
3196
+ "gen": "dots",
3197
+ "scale": 8,
3198
+ "radius": 0.2,
3199
+ "softness": 0.2,
3200
+ "intensity": 0.55,
3201
+ "color": "#000",
3202
+ "blend": "multiply",
3203
+ "stagger": true
3204
+ }
3205
+ ]
3206
+ },
3207
+ "dots": {
3208
+ "doc": "a plain dot grid",
3209
+ "layers": [
3210
+ {
3211
+ "gen": "dots",
3212
+ "scale": 14,
3213
+ "radius": 0.16,
3214
+ "softness": 0.05,
3215
+ "intensity": 0.35,
3216
+ "color": "#000",
3217
+ "blend": "normal"
3218
+ }
3219
+ ]
3220
+ },
3221
+ "lines": {
3222
+ "doc": "diagonal hairlines",
3223
+ "layers": [
3224
+ {
3225
+ "gen": "lines",
3226
+ "angle": 45,
3227
+ "scale": 8,
3228
+ "thickness": 1,
3229
+ "intensity": 0.3,
3230
+ "color": "#000",
3231
+ "blend": "normal"
3232
+ }
3233
+ ]
3234
+ },
3235
+ "grid": {
3236
+ "doc": "a square grid, like graph paper",
3237
+ "layers": [
3238
+ {
3239
+ "gen": "grid",
3240
+ "scale": 24,
3241
+ "thickness": 1,
3242
+ "intensity": 0.25,
3243
+ "color": "#000",
3244
+ "blend": "normal"
3245
+ }
3246
+ ]
3247
+ },
3248
+ "checker": {
3249
+ "doc": "checkerboard",
3250
+ "layers": [
3251
+ {
3252
+ "gen": "checker",
3253
+ "scale": 24,
3254
+ "intensity": 0.12,
3255
+ "color": "#000",
3256
+ "blend": "normal"
3257
+ }
3258
+ ]
3259
+ },
3260
+ "crosshatch": {
3261
+ "doc": "crossed hairlines",
3262
+ "layers": [
3263
+ {
3264
+ "gen": "lines",
3265
+ "angle": 45,
3266
+ "scale": 6,
3267
+ "thickness": 1,
3268
+ "intensity": 0.28,
3269
+ "color": "#000",
3270
+ "blend": "normal"
3271
+ },
3272
+ {
3273
+ "gen": "lines",
3274
+ "angle": -45,
3275
+ "scale": 6,
3276
+ "thickness": 1,
3277
+ "intensity": 0.28,
3278
+ "color": "#000",
3279
+ "blend": "normal"
3280
+ }
3281
+ ]
3282
+ },
3283
+ "stripes": {
3284
+ "doc": "bold diagonal stripes",
3285
+ "layers": [
3286
+ {
3287
+ "gen": "lines",
3288
+ "angle": 45,
3289
+ "scale": 32,
3290
+ "thickness": 16,
3291
+ "intensity": 0.12,
3292
+ "color": "#000",
3293
+ "blend": "normal"
3294
+ }
3295
+ ]
3296
+ },
3297
+ "waves": {
3298
+ "doc": "soft repeating waves",
3299
+ "layers": [
3300
+ {
3301
+ "gen": "waves",
3302
+ "scale": 40,
3303
+ "thickness": 1.2,
3304
+ "intensity": 0.3,
3305
+ "color": "#000",
3306
+ "blend": "normal"
3307
+ }
3308
+ ]
3309
+ },
3310
+ "hex": {
3311
+ "doc": "honeycomb",
3312
+ "layers": [
3313
+ {
3314
+ "gen": "hex",
3315
+ "scale": 28,
3316
+ "thickness": 1,
3317
+ "intensity": 0.28,
3318
+ "color": "#000",
3319
+ "blend": "normal"
3320
+ }
3321
+ ]
3322
+ },
3323
+ "bricks": {
3324
+ "doc": "a red brick wall, light mortar",
3325
+ "layers": [
3326
+ {
3327
+ "gen": "bump",
3328
+ "scale": 3,
3329
+ "octaves": 3,
3330
+ "depth": 2.2,
3331
+ "light": 135,
3332
+ "intensity": 0.55,
3333
+ "color": "#000",
3334
+ "color2": "#fff",
3335
+ "blend": "overlay",
3336
+ "seed": 3
3337
+ },
3338
+ {
3339
+ "gen": "bricks",
3340
+ "scale": 56,
3341
+ "thickness": 3.5,
3342
+ "intensity": 1,
3343
+ "color": "#d9d0c4",
3344
+ "color2": "#a4402b",
3345
+ "blend": "normal"
3346
+ }
3347
+ ]
3348
+ },
3349
+ "bricks-old": {
3350
+ "doc": "weathered dark brick",
3351
+ "layers": [
3352
+ {
3353
+ "gen": "noise",
3354
+ "scale": 40,
3355
+ "octaves": 4,
3356
+ "contrast": 1.5,
3357
+ "intensity": 0.5,
3358
+ "color": "#000",
3359
+ "color2": "#fff",
3360
+ "blend": "soft-light",
3361
+ "seed": 12
3362
+ },
3363
+ {
3364
+ "gen": "bump",
3365
+ "scale": 3,
3366
+ "octaves": 4,
3367
+ "depth": 3.2,
3368
+ "light": 135,
3369
+ "intensity": 0.7,
3370
+ "color": "#000",
3371
+ "color2": "#fff",
3372
+ "blend": "overlay",
3373
+ "seed": 5
3374
+ },
3375
+ {
3376
+ "gen": "bricks",
3377
+ "scale": 52,
3378
+ "thickness": 4,
3379
+ "intensity": 1,
3380
+ "color": "#6d665e",
3381
+ "color2": "#74301f",
3382
+ "blend": "normal"
3383
+ }
3384
+ ]
3385
+ },
3386
+ "herringbone": {
3387
+ "doc": "parquet laid in a zigzag, every board a slightly different shade",
3388
+ "layers": [
3389
+ {
3390
+ "gen": "noise",
3391
+ "scale": 1.2,
3392
+ "octaves": 3,
3393
+ "contrast": 1.4,
3394
+ "stretch": [
3395
+ 30,
3396
+ 1
3397
+ ],
3398
+ "intensity": 0.25,
3399
+ "color": "#000",
3400
+ "color2": "#fff",
3401
+ "blend": "overlay",
3402
+ "seed": 4
3403
+ },
3404
+ {
3405
+ "gen": "herringbone",
3406
+ "scale": 22,
3407
+ "thickness": 1.4,
3408
+ "intensity": 1,
3409
+ "color": "#2a1a0e",
3410
+ "color2": "#b9854d",
3411
+ "seed": 4,
3412
+ "blend": "normal"
3413
+ }
3414
+ ]
3415
+ },
3416
+ "plaid": {
3417
+ "doc": "simple plaid: wide bands crossing, thin lines between",
3418
+ "layers": [
3419
+ {
3420
+ "gen": "plaid",
3421
+ "scale": 64,
3422
+ "thickness": 2,
3423
+ "intensity": 0.5,
3424
+ "color": "#9b1b1b",
3425
+ "blend": "multiply"
3426
+ }
3427
+ ]
3428
+ },
3429
+ "tartan": {
3430
+ "doc": "a red tartan with green and navy bands",
3431
+ "layers": [
3432
+ {
3433
+ "gen": "plaid",
3434
+ "scale": 96,
3435
+ "thickness": 3,
3436
+ "radius": 0.18,
3437
+ "intensity": 0.6,
3438
+ "color": "#0f1f55",
3439
+ "blend": "multiply"
3440
+ },
3441
+ {
3442
+ "gen": "plaid",
3443
+ "scale": 48,
3444
+ "thickness": 2,
3445
+ "radius": 0.3,
3446
+ "intensity": 0.65,
3447
+ "color": "#12503a",
3448
+ "blend": "multiply"
3449
+ },
3450
+ {
3451
+ "gen": "plaid",
3452
+ "scale": 48,
3453
+ "thickness": 1,
3454
+ "radius": 0.001,
3455
+ "intensity": 1,
3456
+ "color": "#a11d24",
3457
+ "color2": "#a11d24",
3458
+ "blend": "normal"
3459
+ }
3460
+ ]
3461
+ },
3462
+ "gingham": {
3463
+ "doc": "small checks, the picnic cloth",
3464
+ "layers": [
3465
+ {
3466
+ "gen": "plaid",
3467
+ "scale": 28,
3468
+ "thickness": 1,
3469
+ "radius": 0.5,
3470
+ "intensity": 0.5,
3471
+ "color": "#c0392b",
3472
+ "color2": "#ffffff",
3473
+ "blend": "normal"
3474
+ }
3475
+ ]
3476
+ },
3477
+ "scales": {
3478
+ "doc": "overlapping scales",
3479
+ "layers": [
3480
+ {
3481
+ "gen": "scales",
3482
+ "scale": 36,
3483
+ "thickness": 1.2,
3484
+ "intensity": 0.4,
3485
+ "color": "#000",
3486
+ "blend": "normal"
3487
+ }
3488
+ ]
3489
+ },
3490
+ "mermaid": {
3491
+ "doc": "teal fish scales",
3492
+ "layers": [
3493
+ {
3494
+ "gen": "scales",
3495
+ "scale": 34,
3496
+ "thickness": 1.4,
3497
+ "intensity": 1,
3498
+ "color": "#0a5560",
3499
+ "color2": "#1fa8a0",
3500
+ "blend": "normal"
3501
+ }
3502
+ ]
3503
+ },
3504
+ "shingles": {
3505
+ "doc": "roof shingles",
3506
+ "layers": [
3507
+ {
3508
+ "gen": "scales",
3509
+ "scale": 60,
3510
+ "thickness": 2,
3511
+ "intensity": 1,
3512
+ "color": "#201c1a",
3513
+ "color2": "#5b4a42",
3514
+ "blend": "normal"
3515
+ }
3516
+ ]
3517
+ },
3518
+ "chevron": {
3519
+ "doc": "zigzag bands",
3520
+ "layers": [
3521
+ {
3522
+ "gen": "chevron",
3523
+ "scale": 40,
3524
+ "thickness": 6,
3525
+ "intensity": 0.3,
3526
+ "color": "#000",
3527
+ "blend": "normal"
3528
+ }
3529
+ ]
3530
+ },
3531
+ "argyle": {
3532
+ "doc": "a diamond lattice, filled",
3533
+ "layers": [
3534
+ {
3535
+ "gen": "diamonds",
3536
+ "scale": 44,
3537
+ "thickness": 1.4,
3538
+ "intensity": 0.9,
3539
+ "color": "#e8e0d0",
3540
+ "color2": "#2f3e6b",
3541
+ "blend": "normal"
3542
+ }
3543
+ ]
3544
+ },
3545
+ "lowpoly": {
3546
+ "doc": "low-poly facets in two tones",
3547
+ "layers": [
3548
+ {
3549
+ "gen": "triangles",
3550
+ "scale": 72,
3551
+ "intensity": 0.5,
3552
+ "color": "#000",
3553
+ "color2": "#fff",
3554
+ "seed": 3,
3555
+ "blend": "overlay"
3556
+ }
3557
+ ]
3558
+ },
3559
+ "tiles": {
3560
+ "doc": "square tiles with grout",
3561
+ "layers": [
3562
+ {
3563
+ "gen": "bump",
3564
+ "scale": 40,
3565
+ "octaves": 2,
3566
+ "depth": 1.4,
3567
+ "light": 135,
3568
+ "intensity": 0.4,
3569
+ "color": "#000",
3570
+ "color2": "#fff",
3571
+ "blend": "overlay",
3572
+ "seed": 6
3573
+ },
3574
+ {
3575
+ "gen": "grid",
3576
+ "scale": 64,
3577
+ "thickness": 3,
3578
+ "intensity": 0.5,
3579
+ "color": "#000",
3580
+ "blend": "normal"
3581
+ }
3582
+ ]
3583
+ },
3584
+ "blueprint": {
3585
+ "doc": "drafting grid, fine and coarse",
3586
+ "layers": [
3587
+ {
3588
+ "gen": "grid",
3589
+ "scale": 100,
3590
+ "thickness": 1.4,
3591
+ "intensity": 0.5,
3592
+ "color": "#ffffff",
3593
+ "blend": "normal"
3594
+ },
3595
+ {
3596
+ "gen": "grid",
3597
+ "scale": 20,
3598
+ "thickness": 1,
3599
+ "intensity": 0.22,
3600
+ "color": "#ffffff",
3601
+ "blend": "normal"
3602
+ }
3603
+ ]
3604
+ },
3605
+ "granite": {
3606
+ "doc": "speckled granite",
3607
+ "layers": [
3608
+ {
3609
+ "gen": "noise",
3610
+ "scale": 1.8,
3611
+ "octaves": 1,
3612
+ "contrast": 9,
3613
+ "threshold": 0.62,
3614
+ "intensity": 0.5,
3615
+ "color": "rgba(0,0,0,0)",
3616
+ "color2": "#000",
3617
+ "blend": "multiply",
3618
+ "seed": 2
3619
+ },
3620
+ {
3621
+ "gen": "noise",
3622
+ "scale": 1.8,
3623
+ "octaves": 1,
3624
+ "contrast": 9,
3625
+ "threshold": 0.7,
3626
+ "intensity": 0.55,
3627
+ "color": "rgba(255,255,255,0)",
3628
+ "color2": "#fff",
3629
+ "blend": "screen",
3630
+ "seed": 7
3631
+ },
3632
+ {
3633
+ "gen": "bump",
3634
+ "scale": 8,
3635
+ "octaves": 3,
3636
+ "depth": 1.2,
3637
+ "light": 135,
3638
+ "intensity": 0.4,
3639
+ "color": "#000",
3640
+ "color2": "#fff",
3641
+ "blend": "overlay",
3642
+ "seed": 4
3643
+ }
3644
+ ]
3645
+ },
3646
+ "slate": {
3647
+ "doc": "layered slate",
3648
+ "layers": [
3649
+ {
3650
+ "gen": "bump",
3651
+ "scale": 18,
3652
+ "octaves": 4,
3653
+ "depth": 2.4,
3654
+ "stretch": [
3655
+ 2.4,
3656
+ 1
3657
+ ],
3658
+ "light": 135,
3659
+ "intensity": 0.8,
3660
+ "color": "#000",
3661
+ "color2": "#fff",
3662
+ "blend": "overlay",
3663
+ "seed": 5
3664
+ },
3665
+ {
3666
+ "gen": "noise",
3667
+ "scale": 90,
3668
+ "octaves": 3,
3669
+ "contrast": 1.4,
3670
+ "intensity": 0.35,
3671
+ "color": "#000",
3672
+ "color2": "#fff",
3673
+ "blend": "soft-light",
3674
+ "seed": 8
3675
+ }
3676
+ ]
3677
+ },
3678
+ "sandstone": {
3679
+ "doc": "banded sandstone",
3680
+ "layers": [
3681
+ {
3682
+ "gen": "bump",
3683
+ "scale": 3,
3684
+ "octaves": 3,
3685
+ "depth": 1.6,
3686
+ "light": 130,
3687
+ "intensity": 0.5,
3688
+ "color": "#000",
3689
+ "color2": "#fff",
3690
+ "blend": "overlay",
3691
+ "seed": 9
3692
+ },
3693
+ {
3694
+ "gen": "noise",
3695
+ "scale": 10,
3696
+ "octaves": 3,
3697
+ "contrast": 1.6,
3698
+ "stretch": [
3699
+ 30,
3700
+ 1
3701
+ ],
3702
+ "intensity": 0.8,
3703
+ "color": "#8a5a3a",
3704
+ "color2": "#e2b784",
3705
+ "blend": "multiply",
3706
+ "seed": 14
3707
+ }
3708
+ ]
3709
+ },
3710
+ "terrazzo": {
3711
+ "doc": "chips of stone in cement",
3712
+ "layers": [
3713
+ {
3714
+ "gen": "terrazzo",
3715
+ "scale": 240,
3716
+ "density": 1,
3717
+ "intensity": 0.9,
3718
+ "color": "#c9a98f",
3719
+ "color2": "#6b7a8f",
3720
+ "seed": 6,
3721
+ "blend": "normal"
3722
+ }
3723
+ ]
3724
+ },
3725
+ "confetti": {
3726
+ "doc": "bright scraps scattered, flat",
3727
+ "layers": [
3728
+ {
3729
+ "gen": "terrazzo",
3730
+ "scale": 260,
3731
+ "density": 0.5,
3732
+ "intensity": 1,
3733
+ "color": "#ff4d6d",
3734
+ "color2": "#4dabf7",
3735
+ "seed": 11,
3736
+ "blend": "normal"
3737
+ }
3738
+ ]
3739
+ },
3740
+ "rust": {
3741
+ "doc": "rusted metal",
3742
+ "layers": [
3743
+ {
3744
+ "gen": "bump",
3745
+ "scale": 4,
3746
+ "octaves": 4,
3747
+ "depth": 3,
3748
+ "light": 135,
3749
+ "intensity": 0.6,
3750
+ "color": "#000",
3751
+ "color2": "#fff",
3752
+ "blend": "overlay",
3753
+ "seed": 3
3754
+ },
3755
+ {
3756
+ "gen": "noise",
3757
+ "scale": 70,
3758
+ "octaves": 5,
3759
+ "contrast": 1.9,
3760
+ "intensity": 0.85,
3761
+ "color": "#3b1d0b",
3762
+ "color2": "#b4602a",
3763
+ "blend": "multiply",
3764
+ "seed": 5
3765
+ }
3766
+ ]
3767
+ },
3768
+ "corrugated": {
3769
+ "doc": "corrugated metal sheet",
3770
+ "layers": [
3771
+ {
3772
+ "gen": "lines",
3773
+ "angle": 90,
3774
+ "scale": 12,
3775
+ "thickness": 6,
3776
+ "softness": 1,
3777
+ "intensity": 0.3,
3778
+ "color": "#000",
3779
+ "blend": "overlay"
3780
+ }
3781
+ ]
3782
+ },
3783
+ "cork": {
3784
+ "doc": "cork board",
3785
+ "layers": [
3786
+ {
3787
+ "gen": "bump",
3788
+ "scale": 4,
3789
+ "octaves": 3,
3790
+ "depth": 3,
3791
+ "light": 135,
3792
+ "intensity": 0.7,
3793
+ "color": "#000",
3794
+ "color2": "#fff",
3795
+ "blend": "overlay",
3796
+ "seed": 2
3797
+ },
3798
+ {
3799
+ "gen": "noise",
3800
+ "scale": 3,
3801
+ "octaves": 3,
3802
+ "contrast": 2.2,
3803
+ "intensity": 0.8,
3804
+ "color": "#8a5a2b",
3805
+ "color2": "#d6a566",
3806
+ "blend": "multiply",
3807
+ "seed": 8
3808
+ }
3809
+ ]
3810
+ },
3811
+ "kraft": {
3812
+ "doc": "brown packing paper",
3813
+ "layers": [
3814
+ {
3815
+ "gen": "noise",
3816
+ "scale": 1.4,
3817
+ "octaves": 3,
3818
+ "contrast": 1.4,
3819
+ "stretch": [
3820
+ 6,
3821
+ 1
3822
+ ],
3823
+ "intensity": 0.35,
3824
+ "color": "#000",
3825
+ "color2": "#fff",
3826
+ "blend": "overlay",
3827
+ "seed": 2
3828
+ },
3829
+ {
3830
+ "gen": "noise",
3831
+ "scale": 60,
3832
+ "octaves": 3,
3833
+ "contrast": 1.3,
3834
+ "intensity": 0.25,
3835
+ "color": "#6b4a2a",
3836
+ "color2": "#d1a874",
3837
+ "blend": "multiply",
3838
+ "seed": 7
3839
+ }
3840
+ ]
3841
+ },
3842
+ "felt": {
3843
+ "doc": "soft wool felt",
3844
+ "layers": [
3845
+ {
3846
+ "gen": "noise",
3847
+ "scale": 1,
3848
+ "octaves": 4,
3849
+ "contrast": 1.2,
3850
+ "intensity": 0.45,
3851
+ "color": "#000",
3852
+ "color2": "#fff",
3853
+ "blend": "overlay",
3854
+ "seed": 3
3855
+ },
3856
+ {
3857
+ "gen": "noise",
3858
+ "scale": 40,
3859
+ "octaves": 3,
3860
+ "contrast": 1.2,
3861
+ "intensity": 0.2,
3862
+ "color": "#000",
3863
+ "color2": "#fff",
3864
+ "blend": "soft-light",
3865
+ "seed": 9
3866
+ }
3867
+ ]
3868
+ },
3869
+ "burlap": {
3870
+ "doc": "coarse sacking",
3871
+ "layers": [
3872
+ {
3873
+ "gen": "weave",
3874
+ "scale": 7,
3875
+ "thickness": 2.2,
3876
+ "intensity": 0.55,
3877
+ "color": "#3b2a14",
3878
+ "blend": "multiply"
3879
+ },
3880
+ {
3881
+ "gen": "noise",
3882
+ "scale": 30,
3883
+ "octaves": 3,
3884
+ "contrast": 1.5,
3885
+ "stretch": [
3886
+ 6,
3887
+ 1
3888
+ ],
3889
+ "intensity": 0.3,
3890
+ "color": "#000",
3891
+ "color2": "#fff",
3892
+ "blend": "overlay",
3893
+ "seed": 4
3894
+ }
3895
+ ]
3896
+ },
3897
+ "stucco": {
3898
+ "doc": "rough plaster",
3899
+ "layers": [
3900
+ {
3901
+ "gen": "bump",
3902
+ "scale": 9,
3903
+ "octaves": 3,
3904
+ "depth": 3.6,
3905
+ "light": 135,
3906
+ "intensity": 0.7,
3907
+ "color": "#000",
3908
+ "color2": "#fff",
3909
+ "blend": "overlay",
3910
+ "seed": 6
3911
+ },
3912
+ {
3913
+ "gen": "noise",
3914
+ "scale": 120,
3915
+ "octaves": 3,
3916
+ "contrast": 1.2,
3917
+ "intensity": 0.2,
3918
+ "color": "#000",
3919
+ "color2": "#fff",
3920
+ "blend": "soft-light",
3921
+ "seed": 1
3922
+ }
3923
+ ]
3924
+ },
3925
+ "starfield": {
3926
+ "doc": "a night sky of small and large stars",
3927
+ "layers": [
3928
+ {
3929
+ "gen": "starfield",
3930
+ "scale": 300,
3931
+ "thickness": 2.4,
3932
+ "density": 0.25,
3933
+ "intensity": 1,
3934
+ "color": "#cfe3ff",
3935
+ "seed": 9,
3936
+ "blend": "normal"
3937
+ },
3938
+ {
3939
+ "gen": "starfield",
3940
+ "scale": 240,
3941
+ "thickness": 1.2,
3942
+ "density": 1.3,
3943
+ "intensity": 0.9,
3944
+ "color": "#ffffff",
3945
+ "seed": 5,
3946
+ "blend": "normal"
3947
+ }
3948
+ ]
3949
+ },
3950
+ "snow": {
3951
+ "doc": "fresh snow with a few sparkles",
3952
+ "layers": [
3953
+ {
3954
+ "gen": "noise",
3955
+ "scale": 1.2,
3956
+ "octaves": 1,
3957
+ "contrast": 16,
3958
+ "threshold": 0.72,
3959
+ "intensity": 1,
3960
+ "color": "rgba(255,255,255,0)",
3961
+ "color2": "#fff",
3962
+ "blend": "screen",
3963
+ "seed": 5
3964
+ },
3965
+ {
3966
+ "gen": "noise",
3967
+ "scale": 90,
3968
+ "octaves": 4,
3969
+ "contrast": 1.4,
3970
+ "intensity": 0.5,
3971
+ "color": "#9fb6d6",
3972
+ "color2": "#ffffff",
3973
+ "blend": "soft-light",
3974
+ "seed": 3
3975
+ }
3976
+ ]
3977
+ },
3978
+ "grass": {
3979
+ "doc": "blades of grass seen from above",
3980
+ "layers": [
3981
+ {
3982
+ "gen": "noise",
3983
+ "scale": 2,
3984
+ "octaves": 4,
3985
+ "contrast": 2,
3986
+ "stretch": [
3987
+ 1,
3988
+ 9
3989
+ ],
3990
+ "intensity": 0.9,
3991
+ "color": "#2f6b2a",
3992
+ "color2": "#8cc152",
3993
+ "blend": "multiply",
3994
+ "seed": 4
3995
+ }
3996
+ ]
3997
+ },
3998
+ "water": {
3999
+ "doc": "water with light ripples",
4000
+ "layers": [
4001
+ {
4002
+ "gen": "waves",
4003
+ "scale": 56,
4004
+ "thickness": 1.6,
4005
+ "intensity": 0.22,
4006
+ "color": "#ffffff",
4007
+ "blend": "normal"
4008
+ },
4009
+ {
4010
+ "gen": "noise",
4011
+ "scale": 100,
4012
+ "octaves": 4,
4013
+ "contrast": 1.5,
4014
+ "intensity": 0.8,
4015
+ "color": "#0a3d62",
4016
+ "color2": "#3c9bd6",
4017
+ "blend": "multiply",
4018
+ "seed": 6
4019
+ }
4020
+ ]
4021
+ },
4022
+ "rain": {
4023
+ "doc": "slanted rain streaks",
4024
+ "layers": [
4025
+ {
4026
+ "gen": "lines",
4027
+ "angle": 100,
4028
+ "scale": 14,
4029
+ "thickness": 1,
4030
+ "softness": 0.6,
4031
+ "intensity": 0.35,
4032
+ "color": "#ffffff",
4033
+ "blend": "normal"
4034
+ }
4035
+ ]
4036
+ },
4037
+ "lava": {
4038
+ "doc": "glowing cracks in dark rock",
4039
+ "layers": [
4040
+ {
4041
+ "gen": "noise",
4042
+ "scale": 90,
4043
+ "octaves": 4,
4044
+ "contour": 0.05,
4045
+ "threshold": 0.5,
4046
+ "intensity": 1,
4047
+ "color": "#ffb300",
4048
+ "color2": "rgba(255,60,0,0)",
4049
+ "blend": "screen",
4050
+ "seed": 7
4051
+ },
4052
+ {
4053
+ "gen": "noise",
4054
+ "scale": 60,
4055
+ "octaves": 5,
4056
+ "contrast": 1.8,
4057
+ "intensity": 0.9,
4058
+ "color": "#1a0500",
4059
+ "color2": "#7a1a00",
4060
+ "blend": "multiply",
4061
+ "seed": 3
4062
+ }
4063
+ ]
4064
+ },
4065
+ "topo": {
4066
+ "doc": "contour lines, like a map",
4067
+ "layers": [
4068
+ {
4069
+ "gen": "noise",
4070
+ "scale": 140,
4071
+ "octaves": 4,
4072
+ "contour": 0.02,
4073
+ "threshold": 0.4,
4074
+ "intensity": 0.35,
4075
+ "color": "#000",
4076
+ "color2": "rgba(0,0,0,0)",
4077
+ "blend": "normal",
4078
+ "seed": 8
4079
+ },
4080
+ {
4081
+ "gen": "noise",
4082
+ "scale": 140,
4083
+ "octaves": 4,
4084
+ "contour": 0.02,
4085
+ "threshold": 0.5,
4086
+ "intensity": 0.5,
4087
+ "color": "#000",
4088
+ "color2": "rgba(0,0,0,0)",
4089
+ "blend": "normal",
4090
+ "seed": 8
4091
+ },
4092
+ {
4093
+ "gen": "noise",
4094
+ "scale": 140,
4095
+ "octaves": 4,
4096
+ "contour": 0.02,
4097
+ "threshold": 0.6,
4098
+ "intensity": 0.35,
4099
+ "color": "#000",
4100
+ "color2": "rgba(0,0,0,0)",
4101
+ "blend": "normal",
4102
+ "seed": 8
4103
+ }
4104
+ ]
4105
+ }
4106
+ }
4107
+ },
4108
+ "aura": {
4109
+ "fromWords": [
4110
+ "edge",
4111
+ "outline",
4112
+ "bottom",
4113
+ "top",
4114
+ "left",
4115
+ "right",
4116
+ "center",
4117
+ "corners",
4118
+ "thumb"
4119
+ ],
4120
+ "shapes": [
4121
+ "circle",
4122
+ "spark",
4123
+ "square",
4124
+ "ring",
4125
+ "star",
4126
+ "flame",
4127
+ "smoke",
4128
+ "line",
4129
+ "heart",
4130
+ "diamond",
4131
+ "triangle",
4132
+ "plus",
4133
+ "drop",
4134
+ "bubble",
4135
+ "snowflake",
4136
+ "confetti",
4137
+ "leaf"
4138
+ ],
4139
+ "dirWords": {
4140
+ "up": -90,
4141
+ "down": 90,
4142
+ "left": 180,
4143
+ "right": 0,
4144
+ "out": "out",
4145
+ "in": "in"
4146
+ },
4147
+ "blends": [
4148
+ "add",
4149
+ "normal"
4150
+ ],
4151
+ "glowKeys": {
4152
+ "color": {
4153
+ "kind": "color",
4154
+ "words": 1,
4155
+ "doc": "the light colour (default: the element's text colour)"
4156
+ },
4157
+ "radius": {
4158
+ "kind": "number",
4159
+ "words": 1,
4160
+ "min": 1,
4161
+ "max": 400,
4162
+ "doc": "how far the glow reaches, in px"
4163
+ },
4164
+ "intensity": {
4165
+ "kind": "number",
4166
+ "words": 1,
4167
+ "min": 0,
4168
+ "max": 4,
4169
+ "doc": "brightness, 1 is normal"
4170
+ },
4171
+ "pulse": {
4172
+ "kind": "number",
4173
+ "words": 1,
4174
+ "min": 0,
4175
+ "max": 60000,
4176
+ "doc": "milliseconds for one breath in and out; 0 is steady"
4177
+ },
4178
+ "depth": {
4179
+ "kind": "number",
4180
+ "words": 1,
4181
+ "min": 0,
4182
+ "max": 1,
4183
+ "doc": "how deep the pulse goes, 0 to 1"
4184
+ },
4185
+ "flicker": {
4186
+ "kind": "number",
4187
+ "words": 1,
4188
+ "min": 0,
4189
+ "max": 1,
4190
+ "doc": "random flicker, like a flame: 0 is steady, 1 is wild"
4191
+ },
4192
+ "seed": {
4193
+ "kind": "number",
4194
+ "words": 1,
4195
+ "min": 0,
4196
+ "max": 99999,
4197
+ "int": true,
4198
+ "doc": "a different random flicker with the same look"
4199
+ },
4200
+ "texture": {
4201
+ "kind": "texture",
4202
+ "words": 1,
4203
+ "doc": "texture(grain scale 2): break the light up with a texture"
4204
+ },
4205
+ "essential": {
4206
+ "kind": "flag",
4207
+ "words": 0,
4208
+ "doc": "keep animating even when the visitor prefers reduced motion"
4209
+ },
4210
+ "unclipped": {
4211
+ "kind": "flag",
4212
+ "words": 0,
4213
+ "doc": "let the light spill out of a parent that clips (overflow: hidden, auto, scroll). Without it the aura is cut at that edge, like the element itself"
4214
+ },
4215
+ "around": {
4216
+ "kind": "around",
4217
+ "words": 1,
4218
+ "doc": "the shape the light follows: follow (the default: the element's clip-path or rounded corners), box, round, pill, diamond, triangle, hexagon, octagon, pentagon, star, star4, star6, cross, heart, drop, bolt, alpha (an <img> or <canvas>'s visible pixels), or polygon(50% 0, 100% 50%, 50% 100%, 0 50%)"
4219
+ },
4220
+ "inner": {
4221
+ "kind": "flag",
4222
+ "words": 0,
4223
+ "doc": "light inside the edge (a rim) instead of outside it"
4224
+ },
4225
+ "rays": {
4226
+ "kind": "number",
4227
+ "words": 1,
4228
+ "min": 1,
4229
+ "max": 64,
4230
+ "int": true,
4231
+ "doc": "that many beams of light from the middle, slowly turning (see spin)"
4232
+ },
4233
+ "spin": {
4234
+ "kind": "number",
4235
+ "words": 1,
4236
+ "min": -3600,
4237
+ "max": 3600,
4238
+ "doc": "rays turn this many degrees a second (default 10); on particles, how fast each one tumbles"
4239
+ },
4240
+ "ripple": {
4241
+ "kind": "number",
4242
+ "words": 1,
4243
+ "min": 200,
4244
+ "max": 60000,
4245
+ "doc": "rings that spread out from the shape and fade, one every this many milliseconds"
4246
+ },
4247
+ "nohalo": {
4248
+ "kind": "flag",
4249
+ "words": 0,
4250
+ "doc": "only the rays and ripples, without the soft halo"
4251
+ },
4252
+ "always": {
4253
+ "kind": "flag",
4254
+ "words": 0,
4255
+ "doc": "keep emitting even when the element cannot be seen (opacity 0, display: none, visibility: hidden, on it or an ancestor): for an invisible anchor that smoke or sparks come out of"
4256
+ },
4257
+ "z": {
4258
+ "kind": "number",
4259
+ "words": 1,
4260
+ "min": -1000000,
4261
+ "max": 2147483000,
4262
+ "int": true,
4263
+ "doc": "the z-index of the layer this light is drawn on: `z 5` puts it under a hill with z-index 10. It meets elements in the page's root stacking context"
4264
+ }
4265
+ },
4266
+ "particleKeys": {
4267
+ "color": {
4268
+ "kind": "colors",
4269
+ "words": 1,
4270
+ "doc": "colours over a particle's life: color #ff0 -> #f60 -> transparent"
4271
+ },
4272
+ "count": {
4273
+ "kind": "number",
4274
+ "words": 1,
4275
+ "min": 1,
4276
+ "max": 600,
4277
+ "int": true,
4278
+ "doc": "how many particles are alive at once"
4279
+ },
4280
+ "size": {
4281
+ "kind": "range",
4282
+ "words": 2,
4283
+ "min": 0.5,
4284
+ "max": 300,
4285
+ "doc": "diameter in px: size 4, or size 10 4 (start and end of a random range)"
4286
+ },
4287
+ "life": {
4288
+ "kind": "range",
4289
+ "words": 2,
4290
+ "min": 50,
4291
+ "max": 60000,
4292
+ "doc": "how long one lives, in ms: life 900, or life 600 1200"
4293
+ },
4294
+ "speed": {
4295
+ "kind": "range",
4296
+ "words": 2,
4297
+ "min": 0,
4298
+ "max": 3000,
4299
+ "doc": "px per second: speed 60, or speed 30 90"
4300
+ },
4301
+ "dir": {
4302
+ "kind": "dir",
4303
+ "words": 1,
4304
+ "doc": "where they head: up, down, left, right, out, in, or degrees (-90 is up)"
4305
+ },
4306
+ "spread": {
4307
+ "kind": "number",
4308
+ "words": 1,
4309
+ "min": 0,
4310
+ "max": 360,
4311
+ "doc": "how wide the fan of directions is, in degrees"
4312
+ },
4313
+ "gravity": {
4314
+ "kind": "number",
4315
+ "words": 1,
4316
+ "min": -2000,
4317
+ "max": 2000,
4318
+ "doc": "px/s squared; negative pulls up (flames), positive down (snow)"
4319
+ },
4320
+ "drag": {
4321
+ "kind": "number",
4322
+ "words": 1,
4323
+ "min": 0,
4324
+ "max": 10,
4325
+ "doc": "air resistance: how fast particles slow down"
4326
+ },
4327
+ "turbulence": {
4328
+ "kind": "number",
4329
+ "words": 1,
4330
+ "min": 0,
4331
+ "max": 1000,
4332
+ "doc": "random sideways wobble, px/s"
4333
+ },
4334
+ "from": {
4335
+ "kind": "word",
4336
+ "words": 1,
4337
+ "values": [
4338
+ "edge",
4339
+ "outline",
4340
+ "bottom",
4341
+ "top",
4342
+ "left",
4343
+ "right",
4344
+ "center",
4345
+ "corners",
4346
+ "thumb"
4347
+ ],
4348
+ "doc": "where they are born: edge or outline (along the element's real outline, see around), bottom, top, left, right, center, corners, thumb (range sliders)"
4349
+ },
4350
+ "shape": {
4351
+ "kind": "word",
4352
+ "words": 1,
4353
+ "values": [
4354
+ "circle",
4355
+ "spark",
4356
+ "square",
4357
+ "ring",
4358
+ "star",
4359
+ "flame",
4360
+ "smoke",
4361
+ "line",
4362
+ "heart",
4363
+ "diamond",
4364
+ "triangle",
4365
+ "plus",
4366
+ "drop",
4367
+ "bubble",
4368
+ "snowflake",
4369
+ "confetti",
4370
+ "leaf"
4371
+ ],
4372
+ "doc": "circle, spark, square, ring, star, flame, smoke, line, heart, diamond, triangle, plus, drop, bubble, snowflake, confetti or leaf"
4373
+ },
4374
+ "grow": {
4375
+ "kind": "number",
4376
+ "words": 1,
4377
+ "min": 0,
4378
+ "max": 10,
4379
+ "doc": "size at the end of life as a multiple of the start: 0.2 shrinks, 3 swells"
4380
+ },
4381
+ "blend": {
4382
+ "kind": "word",
4383
+ "words": 1,
4384
+ "values": [
4385
+ "add",
4386
+ "normal"
4387
+ ],
4388
+ "doc": "add makes overlapping particles brighter, normal just stacks them"
4389
+ },
4390
+ "glowing": {
4391
+ "kind": "flag",
4392
+ "words": 0,
4393
+ "doc": "soft glowing particles that add light"
4394
+ },
4395
+ "behind": {
4396
+ "kind": "flag",
4397
+ "words": 0,
4398
+ "doc": "draw only outside the element's box, as if the element hid them"
4399
+ },
4400
+ "seed": {
4401
+ "kind": "number",
4402
+ "words": 1,
4403
+ "min": 0,
4404
+ "max": 99999,
4405
+ "int": true,
4406
+ "doc": "a different random pattern with the same look"
4407
+ },
4408
+ "texture": {
4409
+ "kind": "texture",
4410
+ "words": 1,
4411
+ "doc": "texture(grain): grainy particles"
4412
+ },
4413
+ "essential": {
4414
+ "kind": "flag",
4415
+ "words": 0,
4416
+ "doc": "keep animating even when the visitor prefers reduced motion"
4417
+ },
4418
+ "unclipped": {
4419
+ "kind": "flag",
4420
+ "words": 0,
4421
+ "doc": "let the particles leave a parent that clips (overflow: hidden, auto, scroll). Without it they are cut at that edge"
4422
+ },
4423
+ "around": {
4424
+ "kind": "around",
4425
+ "words": 1,
4426
+ "doc": "the shape `from edge` and `from outline` follow: follow (the default), box, round, pill, diamond, triangle, hexagon, octagon, pentagon, star, star4, star6, cross, heart, drop, bolt, or polygon(...)"
4427
+ },
4428
+ "palette": {
4429
+ "kind": "flag",
4430
+ "words": 0,
4431
+ "doc": "each particle takes ONE of the colours (confetti) instead of fading through them"
4432
+ },
4433
+ "spin": {
4434
+ "kind": "number",
4435
+ "words": 1,
4436
+ "min": -3600,
4437
+ "max": 3600,
4438
+ "doc": "how fast each particle tumbles, in degrees a second (random direction)"
4439
+ },
4440
+ "wind": {
4441
+ "kind": "number",
4442
+ "words": 1,
4443
+ "min": -3000,
4444
+ "max": 3000,
4445
+ "doc": "a sideways push, px/s squared; negative pushes left"
4446
+ },
4447
+ "orbit": {
4448
+ "kind": "number",
4449
+ "words": 1,
4450
+ "min": -3600,
4451
+ "max": 3600,
4452
+ "doc": "the particles circle the middle of the element, this many degrees a second"
4453
+ },
4454
+ "always": {
4455
+ "kind": "flag",
4456
+ "words": 0,
4457
+ "doc": "keep emitting even when the element cannot be seen (opacity 0, display: none, visibility: hidden, on it or an ancestor): for an invisible anchor that smoke or sparks come out of"
4458
+ },
4459
+ "z": {
4460
+ "kind": "number",
4461
+ "words": 1,
4462
+ "min": -1000000,
4463
+ "max": 2147483000,
4464
+ "int": true,
4465
+ "doc": "the z-index of the layer these particles are drawn on: `z 5` puts them under a hill with z-index 10. It meets elements in the page's root stacking context"
4466
+ }
4467
+ },
4468
+ "glowDefaults": {
4469
+ "radius": 24,
4470
+ "intensity": 1,
4471
+ "pulse": 0,
4472
+ "depth": 0.35,
4473
+ "flicker": 0,
4474
+ "seed": 1
4475
+ },
4476
+ "particleDefaults": {
4477
+ "count": 40,
4478
+ "size": [
4479
+ 4,
4480
+ 4
4481
+ ],
4482
+ "life": [
4483
+ 900,
4484
+ 900
4485
+ ],
4486
+ "speed": [
4487
+ 40,
4488
+ 40
4489
+ ],
4490
+ "dir": -90,
4491
+ "spread": 30,
4492
+ "gravity": 0,
4493
+ "drag": 0,
4494
+ "turbulence": 0,
4495
+ "from": "edge",
4496
+ "shape": "circle",
4497
+ "grow": 0.3,
4498
+ "blend": null,
4499
+ "glowing": false,
4500
+ "seed": 1
4501
+ },
4502
+ "aroundNames": [
4503
+ "follow",
4504
+ "box",
4505
+ "round",
4506
+ "pill",
4507
+ "alpha",
4508
+ "diamond",
4509
+ "triangle",
4510
+ "hexagon",
4511
+ "octagon",
4512
+ "pentagon",
4513
+ "star",
4514
+ "star4",
4515
+ "star6",
4516
+ "cross",
4517
+ "heart",
4518
+ "drop",
4519
+ "bolt"
4520
+ ]
4521
+ },
4522
+ "slider": {
4523
+ "clauses": {
4524
+ "track": "track [height] [color] [radius <n>|full]",
4525
+ "fill": "fill <color> | fill none",
4526
+ "thumb": "thumb <size> [color]",
4527
+ "shape": "shape circle | square | diamond | pill | bar",
4528
+ "ring": "ring <width> <color>",
4529
+ "glow": "glow <color> <radius>",
4530
+ "ticks": "ticks <count> [color] [length]",
4531
+ "graph": "graph <v1> <v2> ... (each 0 to 100)",
4532
+ "graph-height": "graph-height <px>",
4533
+ "graph-gap": "graph-gap <px>"
4534
+ },
4535
+ "thumbShapes": [
4536
+ "circle",
4537
+ "square",
4538
+ "diamond",
4539
+ "pill",
4540
+ "bar"
4541
+ ]
4542
+ },
4543
+ "effects": {
4544
+ "glassWords": {
4545
+ "blur": "a number: how many px to blur what is behind the element (default 16)",
4546
+ "saturate": "saturate <n>: colour boost for what shows through, 1 is unchanged (default 1.5)",
4547
+ "bright": "bright <n>: brightness of what shows through, 1 is unchanged",
4548
+ "tint": "tint <color> | tint none: the milky layer on top (default: a faint wash of the text colour)"
4549
+ },
4550
+ "tiltWords": {
4551
+ "max": "a number: the largest tilt in degrees (default 8)",
4552
+ "lift": "lift <n>: scale while the pointer is over it, e.g. 1.04",
4553
+ "perspective": "perspective <px>: how strong the 3D depth looks (default 900)",
4554
+ "speed": "speed <0.05 to 1>: how quickly it follows (default 0.18)",
4555
+ "invert": "invert: tilt away from the pointer instead of toward it",
4556
+ "glare": "glare: also keep --dssl-gx and --dssl-gy (0% to 100%) pointing at the pointer, for your own highlight"
4557
+ }
4558
+ },
4559
+ "fonts": {
4560
+ "words": {
4561
+ "weights": "a weight (400), several (400 700), or a variable range (100..900)",
4562
+ "italic": "also load the italic of each weight",
4563
+ "display": "display <swap|block|fallback|optional>: how the text behaves while the font loads (default swap)",
4564
+ "from": "from \"/path/to/file.woff2\": use your own file instead of Google Fonts"
4565
+ }
4566
+ }
4567
+ }
native/spec/01-overview.md ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1. Overview
2
+
3
+ ## 1.1 What DSSL is
4
+
5
+ DSSL is a style language. A DSSL file is plain text. It holds tokens (named design values),
6
+ rules (selector blocks), states, conditions, motions and a few effect descriptions. A DSSL
7
+ implementation reads that text and decides how every element in a document should look.
8
+
9
+ A DSSL file describes a result. It does not say how to reach it. That is the whole idea of a
10
+ style language, and it is what makes native support possible: the author says what, the
11
+ engine decides how.
12
+
13
+ ## 1.2 What CSS does (and what that means for DSSL)
14
+
15
+ CSS is not compiled. There is no step that turns a stylesheet into machine code or into some
16
+ other language that the browser then runs. A browser engine is an interpreter for a
17
+ declarative language, and it keeps the interpreted form alive for as long as the page lives.
18
+
19
+ Here is what a browser does with a stylesheet, in order:
20
+
21
+ 1. **Tokenize and parse.** The engine's own code (C++ in Chrome and Safari, Rust in Firefox)
22
+ reads the text and builds rule objects. The collection of rule objects is the CSSOM.
23
+ 2. **Style resolution.** For every element, the engine finds the rules whose selectors match,
24
+ sorts them by the cascade (origin, layer, specificity, order), picks the winning
25
+ declaration for each property, and resolves it into a computed value. The result is one
26
+ computed style per element.
27
+ 3. **Layout.** Using the computed styles, the engine works out the size and position of every
28
+ box.
29
+ 4. **Paint.** The engine turns boxes and computed values into drawing commands: fill this
30
+ rectangle, draw this text, blur that shadow.
31
+ 5. **Composite.** Painted layers are combined on the GPU and shown.
32
+
33
+ Engines do optimise stage 2. They index rules by class, id and tag so they do not test every
34
+ selector against every element, they cache results, and some compile selectors to machine
35
+ code. None of that changes what a stylesheet means. It is an implementation detail.
36
+
37
+ Every CSS property is built-in code inside the engine. `background` is a paint routine,
38
+ `width` is a layout input, `border-radius` changes the shape that paint clips to. Nothing in
39
+ this path runs JavaScript.
40
+
41
+ ### What native DSSL means
42
+
43
+ Native DSSL means a browser reads DSSL text with its own parser and feeds the result into the
44
+ same stages. Which stages matter depends on the feature:
45
+
46
+ | DSSL feature | CSS stage it joins | What the engine must add |
47
+ |---|---|---|
48
+ | Syntax, tokens, semantic properties (`row:`, `radius:`, `fluid`) | 1 and 2 | A DSSL parser. After that, they are ordinary computed values. |
49
+ | Layers, states, media, containers | 2 | Nothing new: the cascade already has these concepts. |
50
+ | `@signal` | 2 | A way for the page to set named values the cascade can read. |
51
+ | `motion`, `animate` | 2 and 4 | Mapping to the engine's existing keyframe animation. |
52
+ | `texture`, `aura`, `glass`, `slider` | 4 | New drawing code, the way `box-shadow` once was new. |
53
+ | `fold`, `bend-around`, `tilt`, `overlap` | 3 and 4 | Hooks that see box sizes, so they must run during or after layout. |
54
+
55
+ The first four rows are small. The last two rows are where real engineering is needed, and
56
+ chapter 11 says exactly what.
57
+
58
+ ## 1.3 Processing model
59
+
60
+ An implementation takes DSSL source text and a document, and produces styled output. The
61
+ model has six stages. A conforming implementation does not have to run them as separate
62
+ passes, but its results MUST be the same as if it had.
63
+
64
+ ```
65
+ source text
66
+ | 1. decode, strip comments, split into logical lines
67
+ v
68
+ syntax tree (chapter 2)
69
+ | 2. classify headers, resolve values, expand semantic properties,
70
+ | flatten nesting, collect tokens and motions
71
+ v
72
+ resolved document (chapters 3 to 8, output defined in chapter 10)
73
+ | 3. for each element: match rules, apply conditions
74
+ v
75
+ matched rules
76
+ | 4. sort by (layer, order), last declaration per property wins
77
+ v
78
+ declared values (chapter 5)
79
+ | 5. compute: same as CSS from here
80
+ v
81
+ computed style + effect descriptors
82
+ | 6. layout and paint, supplied by the host
83
+ v
84
+ pixels
85
+ ```
86
+
87
+ Stages 1 and 2 need no document. They are pure functions of the source text (and the tokens
88
+ and motions that earlier files defined, see 4.5). That is why they can be tested without a
89
+ browser, and why this repository tests them by comparing outputs.
90
+
91
+ ## 1.4 Conformance levels
92
+
93
+ An implementation claims a level. Each level includes the one before it.
94
+
95
+ | Level | Name | What it must do |
96
+ |---|---|---|
97
+ | 1 | Parser | Stage 1. Produce the syntax tree and the syntax errors defined in chapter 2. |
98
+ | 2 | Resolver | Stages 1 and 2. Produce a resolved document equal to the reference output (chapter 10), including errors and warnings. |
99
+ | 3 | Style engine | Stages 1 to 5. Give every element the declared values chapter 5 defines. |
100
+ | 4 | Renderer | Stage 6 for the effects in chapter 8: texture, aura, slider, glass, tilt, deform. |
101
+
102
+ The reference crate in this repository aims at level 2 first, then 3 and 4. It is not there
103
+ yet: `native/README.md` says exactly where it stands.
104
+
105
+ ## 1.5 Selectors are CSS selectors
106
+
107
+ DSSL does not define a selector language. Selectors are CSS selectors as defined by the
108
+ Selectors specification, and matching is done by the host. This is how the JavaScript engine
109
+ works too: it calls `Element.matches`.
110
+
111
+ Two things sit on top of that:
112
+
113
+ - **State words.** A block header that is exactly one of the state words (chapter 2) means
114
+ "the parent selector plus this pseudo-class". `hover { ... }` inside `.card` means
115
+ `.card:hover`.
116
+ - **`seen`.** `:seen` is not a CSS pseudo-class. It is defined in 5.7.
117
+
118
+ A selector goes through the resolver as text. The resolver only looks at it to find
119
+ pseudo-elements (5.8) and to scan states. It never rewrites one except to join nested
120
+ selectors (5.3).
121
+
122
+ ## 1.6 Versioning
123
+
124
+ The spec has the same version as the language it describes. This draft describes DSSL 0.16.
125
+ A change that makes a previously valid source invalid, or changes what a valid source means,
126
+ is listed in the changelog of the language and raises the second number.
127
+
128
+ ## 1.7 What is out of scope
129
+
130
+ - The selector language (see 1.5).
131
+ - Layout and paint for ordinary CSS properties. DSSL passes those through unchanged.
132
+ - The CSS value grammar. DSSL checks that a word is a known CSS word and gives numbers
133
+ their units. It does not check that a value is valid for its property. The host does that
134
+ when it computes the style.
135
+ - The editor extension, the designer and the build tools (`dssl check`, `dssl build`,
136
+ `dssl import`). Chapter 11 says how the build lane relates to native support.
native/spec/02-syntax.md ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 2. Syntax
2
+
3
+ This chapter turns source text into a syntax tree. It needs no tokens, no document and no
4
+ vocabulary except the lists in the appendices.
5
+
6
+ ## 2.1 Source text
7
+
8
+ - **SYN-1.** Source is a sequence of Unicode characters. Line numbers start at 1 and count
9
+ `\n` characters. A parser that reads bytes MUST decode UTF-8 first. `\r` is not special: a
10
+ `\r\n` file leaves a `\r` at the end of each line, and values are trimmed, so it does no
11
+ harm.
12
+ - **SYN-2.** Every diagnostic carries the file name and a 1-based line number. For a
13
+ construct that spans several lines, the line is the one where the construct started.
14
+
15
+ ## 2.2 Comments
16
+
17
+ There are two comment forms: `// to the end of the line` and `/* a block */`.
18
+
19
+ - **SYN-3.** Comments are removed before anything else is parsed, in one pass over the whole
20
+ source, left to right.
21
+ - **SYN-4.** A quote character (`"` or `'`) that is not inside a comment starts a string. A
22
+ string ends at the next occurrence of the same quote character that is not directly
23
+ preceded by a backslash. Comment markers inside a string are plain text.
24
+ - **SYN-5.** A line comment runs to, but not including, the next `\n`. A block comment runs
25
+ to the first `*/`, or to the end of the source if there is none. Inside a block comment,
26
+ every character except `\n` is replaced by a space and every `\n` is kept. Removing a
27
+ comment therefore never changes the line number of anything after it.
28
+ - **SYN-6.** An unquoted `url(` ... `)` is one token for the comment pass. When the letters
29
+ `url(` (any case) are not directly preceded by a letter, digit, `_` or `-`, and the first
30
+ character after the `(` and any white space is not a quote, the text through the closing `)`
31
+ is kept exactly as written, newlines included, so a `//` inside it (`url(https://x.com/a.png)`)
32
+ is not a comment. A `url(` with no closing `)` is not special. A quoted `url("...")` is
33
+ covered by SYN-4. (Before 0.16.3 the engine treated that `//` as a comment, see D-1.)
34
+
35
+ Comments are removed from everything else, including the body of `raw { }`.
36
+
37
+ ```dssl
38
+ // a line comment
39
+ .card {
40
+ padding: 16 /* a block comment, even in the middle of a line */
41
+ }
42
+ ```
43
+
44
+ ## 2.3 Logical lines
45
+
46
+ The comment-free source is split on `\n` into physical lines. Physical lines are joined into
47
+ logical chunks, and each chunk is processed by 2.4.
48
+
49
+ - **SYN-7.** A blank physical line is skipped. (Inside a `raw { }` capture it is kept. See
50
+ 2.7.)
51
+ - **SYN-8.** A chunk is *incomplete* when any of these holds:
52
+ 1. its parentheses or square brackets are unbalanced (more opening than closing, counting
53
+ outside quoted strings), or
54
+ 2. after trimming trailing white space, it ends in `,`, or
55
+ 3. after trimming trailing white space, it ends in `:`.
56
+ - **SYN-9.** While the buffer is incomplete, the next non-blank physical line is trimmed and
57
+ appended to the buffer with one space between them. When the buffer becomes complete, it is
58
+ processed as one chunk, with the line number of the first physical line. A chunk that is
59
+ incomplete at the end of the file is processed as it stands.
60
+
61
+ Why: this is how multi-line values, multi-line selector lists and `prop:` followed by the
62
+ value on the next line all work with no semicolons.
63
+
64
+ ```dssl
65
+ .card,
66
+ .panel {
67
+ background: linear-gradient(
68
+ to bottom, red, blue
69
+ )
70
+ color:
71
+ color.ink
72
+ }
73
+ ```
74
+
75
+ ## 2.4 Processing a chunk
76
+
77
+ A chunk is a string. The parser keeps a stack of open blocks. The root is always at the
78
+ bottom of the stack. It repeats the following on the chunk until nothing is left:
79
+
80
+ 1. Trim the chunk. If it is empty, stop.
81
+ 2. Find the first top-level `{` and the first top-level `}`. "Top-level" means not inside a
82
+ quoted string and not inside parentheses or square brackets.
83
+ 3. If there is a `}` and either there is no `{` or the `}` comes first:
84
+ - treat the text before it as declarations (2.6) if it is not empty,
85
+ - if the stack holds only the root, report `unexpected "}"` (SYN-10); otherwise pop one
86
+ block,
87
+ - continue with the text after the `}`.
88
+ 4. Else if there is a `{`: the text before it is the *header text*.
89
+ - **SYN-11.** If the header text contains a top-level `;`, everything before the last such
90
+ `;` is treated as declarations (2.6) and the header is the text after it. This is what
91
+ lets `.card { background: red; hover { background: blue } }` work on one line. If the
92
+ remaining header is empty, report `block is missing a header before "{"` and skip the
93
+ `{`.
94
+ - If the header text is empty, report the same error and skip the `{`.
95
+ - Otherwise classify the header (2.5), append a node to the block on top of the stack, and
96
+ run the block-open rule (2.7). If the block is not closed on this line, push it.
97
+ 5. Else the whole chunk is declarations (2.6). Stop.
98
+
99
+ A node always carries the line number of its chunk.
100
+
101
+ ## 2.5 Classifying a header
102
+
103
+ The header is trimmed. The first row that matches wins.
104
+
105
+ | Header | Node kind | Notes |
106
+ |---|---|---|
107
+ | exactly `tokens` | `tokens` | 4.1 |
108
+ | exactly `pin` | `pin` | the must-win layer, 5.2 |
109
+ | exactly `raw` or `@css` | `raw` | verbatim CSS, 2.7 |
110
+ | exactly `fonts` | `fonts` | 8.7 |
111
+ | starts with `behavior ` | `behavior`, name = rest, trimmed | 6.9 |
112
+ | starts with `motion ` | `motion`, name = rest, trimmed | chapter 7 |
113
+ | starts with `@layer ` | `layer`, name = rest, trimmed | 5.2 |
114
+ | starts with `@media ` | `media`, query = rest, trimmed | 5.5 |
115
+ | starts with `@container` | `container`, query = rest after the 10 characters, trimmed | 5.6 |
116
+ | starts with `@signal ` | `signal`, expr = rest, trimmed | 5.9 |
117
+ | starts with any other `@` | `bogus` | error `unknown at-rule "<first word>"`, the whole block is skipped (2.7) |
118
+ | starts with `::` | `pseudo`, name = text after `::` up to the first space or `(` | 5.8. If the name is not a pseudo-element name and not a vendor pseudo, error `unknown pseudo-element "::name"` and the block is skipped |
119
+ | exactly one of the state words | `state`, name = the word | 5.7 |
120
+ | anything else | `selector`, sel = the header | a CSS selector list |
121
+
122
+ The state words, the pseudo-element names and the vendor-pseudo pattern are in appendix A.
123
+
124
+ - **SYN-12.** Matching of `tokens`, `pin`, `raw`, `@css` and `fonts` is on the whole trimmed
125
+ header. `tokens foo {` is a selector. `@media(width>1)` (no space) is an unknown at-rule.
126
+ `@container` is the one at-rule that does not require a space.
127
+
128
+ Unknown at-rules are skipped whole, so nothing inside them leaks out. For verbatim CSS the
129
+ author writes `raw { }`.
130
+
131
+ ```dssl
132
+ raw {
133
+ @supports (display: grid) {
134
+ .box { display: grid; }
135
+ }
136
+ }
137
+ ```
138
+
139
+ ## 2.6 Declarations
140
+
141
+ A declaration text is split on top-level `;`. Inside a `tokens` block (the block on top of
142
+ the stack, or any block below it is a `tokens` block), each piece is further split by the
143
+ token-pair rule (2.8). Each non-empty trimmed piece is one declaration:
144
+
145
+ - **SYN-13.** Find the first top-level `:` in the piece.
146
+ - **SYN-14.** If there is none:
147
+ - `use <name>` where `<name>` matches `[A-Za-z][\w-]*` is a declaration with property
148
+ `use` and value `<name>`.
149
+ - A bare word matching `[a-z][a-z0-9-]*` (case-insensitive) is a declaration with that word
150
+ lowercased as the property and an empty value. (This is how `truncate` and
151
+ `visually-hidden` are written.)
152
+ - Anything else is the error `expected ":" in "<first 40 characters>"` with the hint
153
+ `declarations look like prop: value`.
154
+ - **SYN-15.** The property is the text before the colon, trimmed and lowercased. The value is
155
+ the text after it, trimmed, with its case kept. An empty property is the error
156
+ `declaration is missing a property name`.
157
+ - **SYN-16.** A property that is all digits with an optional fraction (`9`, `2.5`) is legal
158
+ only when some block on the stack is a `tokens` block. Elsewhere it is the error
159
+ `"<prop>" is not a valid property name`, with a hint about token keys.
160
+ - **SYN-17.** Any other property MUST match
161
+ `^(-{1,2}[a-z][a-z0-9.-]*|[a-z][a-z0-9-]*)$` (case-insensitive), otherwise the error
162
+ `"<prop>" is not a valid property name` is reported and the declaration is dropped. Dotted
163
+ names are legal only after a leading `--` (`--color.accent`).
164
+
165
+ A valid declaration becomes a `decl` node `{ prop, value, line }` appended to the block on
166
+ top of the stack.
167
+
168
+ ## 2.7 Block-open rule
169
+
170
+ After a header is classified and its node is appended:
171
+
172
+ - **`raw` and `fonts`.**
173
+ - **SYN-18.** They are legal only at the top level (the stack holds only the root).
174
+ Otherwise report `raw { } must be top-level` (or `fonts { } ...`) with the hint
175
+ `move it out of the enclosing block`, and skip the balanced block.
176
+ - **SYN-19.** The body is captured verbatim into `node.css`. If the matching `}` is on the
177
+ same line as the header, the body is the text between the braces. Otherwise capture
178
+ continues on following physical lines: each one is appended followed by `\n`, blank lines
179
+ included, until the braces balance. The first line contributes only the text after the
180
+ `{`. Brace matching ignores quoted strings. Text after the closing `}` is processed as a
181
+ new chunk. If the file ends first, report `the raw { } block was never closed with "}"`
182
+ and the CSS captured so far is discarded.
183
+ - **`bogus`.**
184
+ - **SYN-20.** The block is skipped: the parser finds the matching `}` (quote-aware) and
185
+ continues after it. If there is none, report
186
+ `the "<first 30 characters>" block was never closed with "}"` and discard the rest of the
187
+ chunk.
188
+ - **Everything else.** The node is pushed on the stack.
189
+
190
+ At the end of the file, if more than the root is on the stack, report
191
+ `<n> block(s) were never closed with "}"`.
192
+
193
+ ## 2.8 Token pairs
194
+
195
+ - **SYN-21.** Inside a `tokens` block, `pill: 999 card: 22` on one line is two declarations.
196
+ A piece is split at a space when all of these hold: a top-level `:` (not part of `::`) has
197
+ already been seen in the piece, the space is outside strings, parentheses and brackets, and
198
+ the text after the space looks like `name:` where `name` matches `[A-Za-z0-9][\w.-]*`
199
+ followed by optional spaces and a `:` that is not `::`.
200
+
201
+ ## 2.9 The syntax tree
202
+
203
+ ```
204
+ root { kind: 'root', children: [...] }
205
+ node { kind, line, children: [...], ...extra }
206
+ decl { kind: 'decl', prop, value, line }
207
+ ```
208
+
209
+ Extra fields by kind: `behavior` and `motion` have `name`, `layer` has `name`, `media` and
210
+ `container` have `query`, `signal` has `expr`, `selector` has `sel`, `state` and `pseudo`
211
+ have `name`, `raw` and `fonts` have `css`. Child order is source order. The tree is data: a
212
+ Level 1 parser is tested by comparing its tree and its errors with the reference.
native/spec/03-values.md ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 3. Values
2
+
3
+ This chapter fixes how a value string such as `0 2 4 color.ink/10` becomes CSS. The algorithm
4
+ is applied to the value of an ordinary declaration, to token values, and to the pieces that
5
+ semantic properties pass on (chapter 6).
6
+
7
+ ## 3.1 The resolution context
8
+
9
+ Every value is resolved with a context:
10
+
11
+ | Field | Meaning |
12
+ |---|---|
13
+ | `prop` | the property name the value belongs to (a token key for token values) |
14
+ | `unitless` | when true, a bare number stays a bare number. When false, it gets a unit. |
15
+ | `bareIdents` | when true, an unknown identifier is allowed through (font names, counter names) |
16
+ | `tokens` | the current token map (chapter 4) |
17
+
18
+ - **VAL-1.** For an ordinary property, `unitless` is true exactly when the property is in the
19
+ unitless list (appendix A). A semantic property chooses its own context for each piece it
20
+ passes on (chapter 6).
21
+ - **VAL-2.** `bareIdents` is true for `font-family`, for any property whose name starts with
22
+ `counter-`, for custom properties (`--name`), and for tokens whose key starts with `font`.
23
+ It is also switched on inside `counter()`, `counters()`, `attr()`, `env()`, `format()`,
24
+ `local()`, `paint()` and `element()`.
25
+
26
+ ## 3.2 The scan
27
+
28
+ The value is scanned left to right. At each position, the first of these rules that applies
29
+ is used.
30
+
31
+ 1. **White space** is copied as it is.
32
+ 2. **Strings.** `"` or `'` starts a string that runs to the next occurrence of the same
33
+ quote. A backslash makes the character after it part of the string, so `"a\"b"` is one
34
+ string. It is copied verbatim. The same applies to strings inside function arguments and in
35
+ layer lists. (Before 0.16.3 the backslash was ignored here, see D-5.)
36
+ 3. **Numbers.** A number starts at a digit, at `.` followed by a digit, or at `-` or `+`
37
+ followed by a digit or `.`. It continues over digits and `.`. If letters or `%` follow,
38
+ they are the unit and are lowercased. The output is the number text plus:
39
+ - the unit, if there is one,
40
+ - otherwise nothing, if `unitless` is true,
41
+ - otherwise `ms`, if `prop` is a time property (appendix A),
42
+ - otherwise `px`.
43
+ 4. **Identifiers.** An identifier matches
44
+ `^-?(?:[A-Za-z_]|--[A-Za-z_])[A-Za-z0-9_.\\-]*`. Note that `.` and `-` are part of an
45
+ identifier, so `color.ink` and `ease-out` are single identifiers, and so is `a-1`.
46
+ What happens next, in order:
47
+ 1. A name starting `--` is copied as it is (a custom property).
48
+ 2. If the next character is `(`, it is a **function call** (3.3).
49
+ 3. If the identifier is a **token** (3.4), it is replaced by a variable reference.
50
+ 4. If it is a known **keyword** (3.5), it is copied as it is.
51
+ 5. If `bareIdents` is true and the identifier is plain letters, digits, `_` and `-`
52
+ starting with a letter, it is copied as it is.
53
+ 6. Otherwise it is an error (3.6). The identifier is copied and scanning continues.
54
+ 5. **Hex colors.** `#` followed by hex digits is copied. An alpha suffix follows the rules in
55
+ 3.4.
56
+ 6. **Anything else** (`/`, `,`, `+`, `*`, `%`, `(`, `)`, `->`) is copied one character at a
57
+ time.
58
+
59
+ - **VAL-3.** Rule 3 means `0` becomes `0px`, `margin: 0 auto` becomes `0px auto`, and
60
+ `opacity: 0.5` stays `0.5` because `opacity` is unitless.
61
+ - **VAL-4.** `--name` custom property values are resolved like any other, with
62
+ `unitless: false` and `bareIdents: true`.
63
+
64
+ ```dssl
65
+ .a {
66
+ padding: 0
67
+ margin: 0 auto
68
+ opacity: 0.5
69
+ line-height: 1.5
70
+ transition-delay: 200
71
+ }
72
+ ```
73
+
74
+ The five lines resolve to `0px`, `0px auto`, `0.5`, `1.5` and `200ms`.
75
+
76
+ ## 3.3 Function calls
77
+
78
+ The text from the `(` to its matching `)` is the argument text. Matching counts nested
79
+ parentheses and skips quoted strings.
80
+
81
+ - **VAL-5.** `url( )` is atomic. The output is `url(` + the argument text trimmed + `)`. Nothing
82
+ inside is looked at.
83
+ - **VAL-6.** `repeat(N, tracks)`, when the first argument is a number, keeps `N` as it is
84
+ (never a length) and resolves the rest with the same context. When the first argument is
85
+ not a plain number (`auto-fill`, `auto-fit`), the call falls through to VAL-7.
86
+ - **VAL-7.** Any other call resolves its argument text recursively. The inner context is:
87
+ - `unitless: true` for the functions in the *unitless functions* list (colors, filters
88
+ that take numbers, `scale*`, `steps`, `cubic-bezier`, `linear`, `radial`, `conic`),
89
+ - `unitless: false` for the *length functions* list (`calc`, `min`, `max`, `clamp`, the
90
+ other math functions, `blur`, `drop-shadow`),
91
+ - the outer context's `unitless` for any other function.
92
+ Function names are matched lowercase. The output keeps the written name.
93
+
94
+ Both lists are in appendix A.
95
+
96
+ ```dssl
97
+ .a {
98
+ width: calc(100% - 24)
99
+ background: oklch(62% 0.19 35)
100
+ grid-template-columns: repeat(4, 1fr)
101
+ transform: scale(1.2)
102
+ }
103
+ ```
104
+
105
+ `calc(100% - 24)` becomes `calc(100% - 24px)`. The `oklch` numbers and the `scale` factor
106
+ stay bare. `repeat(4, 1fr)` keeps its `4`.
107
+
108
+ ## 3.4 Tokens and alpha
109
+
110
+ A reference to a token is the token's key written as an identifier (`color.ink`, `space.4`).
111
+ How a key is found is in 4.4. A reference becomes `var(--dssl-<key>)`, where every `.` in
112
+ the key is replaced by `-`.
113
+
114
+ - **VAL-8.** *Alpha modifier.* A token reference, a named color, `currentcolor` or a hex
115
+ color may be followed by `/` and a number, with optional white space and an optional `%`:
116
+ `color.ink/20`, `white/12`, `#ff0000/50`. The whole thing becomes
117
+ `color-mix(in oklab, <color> N%, transparent)`, where `N` is the number text as written.
118
+ The number is always a percentage, with or without the `%` sign.
119
+ - **VAL-9.** A named color is a keyword for which the color table (appendix A) has an entry.
120
+
121
+ ```dssl
122
+ tokens { color { x: #123456 } }
123
+ .a {
124
+ color: color.x/50
125
+ background: #ff0000/50
126
+ border-color: white/12
127
+ }
128
+ ```
129
+
130
+ These three become `color-mix(in oklab, var(--dssl-color-x) 50%, transparent)`,
131
+ `color-mix(in oklab, #ff0000 50%, transparent)` and
132
+ `color-mix(in oklab, white 12%, transparent)`.
133
+
134
+ ## 3.5 Keywords
135
+
136
+ The keyword set (appendix A) holds every word DSSL accepts as a bare CSS value: color names,
137
+ display values, easing names, units of measure words and everything the CSS property
138
+ definitions mention. It is generated from the engine's vocabulary module.
139
+
140
+ - **VAL-10.** A keyword matches if the identifier is in the set, or its lowercase form is.
141
+
142
+ Why: DSSL is loud about typos. An identifier that is not a token, not a keyword and not
143
+ allowed by `bareIdents` is a mistake, and 3.6 says so.
144
+
145
+ ## 3.6 Unknown identifiers
146
+
147
+ - **VAL-11.** An unknown identifier is the error `unknown value "<id>" for property "<prop>"`.
148
+ The hint is the best did-you-mean (9.3) over the token keys followed by the keywords, or no
149
+ hint if nothing is close. The identifier is copied to the output, so the value still
150
+ resolves as far as it can.
151
+
152
+ ```dssl error
153
+ .a { color: colour }
154
+ ```
155
+
156
+ ## 3.7 Fluid sizes
157
+
158
+ `fluid` is sugar for a `clamp()` that moves linearly between two viewport widths. It has two
159
+ spellings.
160
+
161
+ - `font-size: fluid 16 24`: the value is `fluid` followed by arguments. It is allowed on the
162
+ properties in the *fluid properties* list (appendix A).
163
+ - `fluid: 16, 24`: the semantic property `fluid` (6.4) writes `font-size`.
164
+
165
+ The arguments are, in order: the first size, the second size, the lower viewport width
166
+ (default `360`), the upper viewport width (default `1200`). Arguments are separated by
167
+ white space or commas.
168
+
169
+ - **VAL-12.** A size is a number with an optional unit of `px`, `rem` or `em`. A bare number
170
+ is `px`. A `%` is an error (`cannot be a percentage, use px, rem or em`). The two sizes
171
+ MUST have the same unit (`mixes units, give both sizes the same unit`). The viewport
172
+ widths are in `px` and the upper MUST be larger than the lower.
173
+ - **VAL-13.** Let `a`, `b` be the sizes, `v0`, `v1` the viewport widths, `f` = 1 for `px`
174
+ and 16 for `rem` and `em`. Then
175
+ `slope = ((b - a) * f) / (v1 - v0)`, `vw = slope * 100` and
176
+ `intercept = a - (slope * v0) / f`. The output is
177
+ `clamp(<min><unit>, calc(<intercept><unit> + <vw>vw), <max><unit>)`, where `min` and `max`
178
+ are the smaller and larger of `a` and `b`, and every number is written with at most three
179
+ decimal places and no trailing zeros.
180
+ - **VAL-14.** On an error the property gets the value `0px` and the error is reported.
181
+
182
+ ```dssl
183
+ h1 { font-size: fluid 28 44 }
184
+ .stack { gap: fluid 8 16 }
185
+ .hero { font-size: fluid 16 24 400 800 }
186
+ ```
187
+
188
+ For `fluid 16 24` the output is `clamp(16px, calc(12.571px + 0.952vw), 24px)`.
189
+
190
+ ## 3.8 Important
191
+
192
+ - **VAL-15.** The text `!important` (any case, any space after `!`) outside a quoted string
193
+ is not valid DSSL. The declaration is dropped and the error
194
+ `!important does not exist in DSSL - "<prop>: <value>" was NOT applied` is reported, with
195
+ the hint to use `pin { }`.
196
+
197
+ ```dssl error
198
+ .a { color: red !important }
199
+ ```
native/spec/04-tokens.md ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 4. Tokens
2
+
3
+ A token is a named design value: a color, a space, a radius, a shadow, a font stack. Tokens
4
+ become CSS custom properties, so they follow the page's normal inheritance and can be
5
+ re-declared in any subtree.
6
+
7
+ ## 4.1 The tokens block
8
+
9
+ ```dssl
10
+ tokens {
11
+ color {
12
+ ink: #16161d
13
+ accent: oklch(62% 0.19 35)
14
+ }
15
+ space { 1: 4 2: 8 9: 96 }
16
+ radius { md: 14 }
17
+ shadow { md: 0 2 4 color.ink/10, 0 4 12 color.ink/8 }
18
+ }
19
+ ```
20
+
21
+ - **TOK-1.** A `tokens` block may appear at the top level, or inside `@media`, `@container`,
22
+ `@signal` and `@layer` blocks that themselves have no selector around them. Inside a
23
+ selector it is the error `tokens must be top-level or inside @media / @signal`, and the
24
+ block is ignored.
25
+ - **TOK-2.** Inside a `tokens` block, a declaration `name: value` defines the token whose key
26
+ is the path to that block plus the name. A nested block whose header is a plain word
27
+ (`color { ... }`) adds that word to the path. A nested `tokens` block does the same with
28
+ the header text `tokens`. Any other nested block kind is the error
29
+ `unexpected "<kind>" inside tokens`.
30
+ - **TOK-3.** Numeric names (`1`, `9`) are legal keys (SYN-16). `space { 9: 96 }` defines
31
+ `space.9`.
32
+ - **TOK-4.** The value of a token is kept as written. It is resolved (chapter 3) when the
33
+ token is emitted (4.3), with `prop` set to the token key and `unitless` false.
34
+
35
+ Why: tokens resolve late, so a token may refer to another token, to alpha, or to a function,
36
+ and each reference is checked in the place it is used.
37
+
38
+ ## 4.2 Keys and variable names
39
+
40
+ - **TOK-5.** A token key is a dotted path such as `color.ink` or `shadow.md`.
41
+ - **TOK-6.** The variable name for a key is `--dssl-` followed by the key with each `.`
42
+ replaced by `-`. So `color.ink` is `--dssl-color-ink` and `space.9` is `--dssl-space-9`.
43
+ Two keys that differ only by `.` against `-` (`a.b-c` and `a.b.c`) therefore share a
44
+ variable. Authors SHOULD avoid that.
45
+
46
+ ## 4.3 Emission
47
+
48
+ A token that is defined becomes a declaration on `:root` in the layer `tokens`.
49
+
50
+ - **TOK-7.** Before reading any rule, the resolver collects the tokens of every `tokens` block
51
+ in the file, however deeply nested in conditions, into one map. When the same key is
52
+ defined twice, the later definition in source order replaces the earlier one in that map.
53
+ The map is also seeded with the tokens the host passes in (4.5), starting from the
54
+ built-in defaults.
55
+ - **TOK-8.** Each `tokens` block, when it is reached during the walk (5.4), emits one rule
56
+ with selector `:root`, layer `tokens`, the media, containers and signals of the context it
57
+ is in, and one declaration per key it defines itself, in the order the keys were written.
58
+ The value of each declaration is the value that block wrote, not the final value in the
59
+ map.
60
+ - **TOK-9.** Before the walk, the resolver also builds the *registry rule*: selector `:root`,
61
+ layer `tokens`, no conditions, one declaration for every key in the map in map order. It
62
+ takes the first order number (0). It is returned beside the rules and is not part of the
63
+ rules list. Hosts install it first, so every token has a value even before its block is
64
+ reached. Because it is built from the map, it carries the last definition of each key.
65
+ - **TOK-10.** An error in a token value is reported at the line where the token was written,
66
+ and only by the file that defines it. A token that comes from the host (4.5) is not checked
67
+ again.
68
+
69
+ ```dssl
70
+ tokens { color { bg: white } }
71
+ @media dark { tokens { color { bg: black } } }
72
+ .a { background: color.bg }
73
+ ```
74
+
75
+ This yields one `:root` rule for `white` and one for `black` under the dark condition. Both
76
+ are in the layer `tokens`, and the second has a later order, so it wins when dark applies.
77
+
78
+ ## 4.4 Looking up a token
79
+
80
+ - **TOK-11.** An identifier in a value is a token reference when its text is exactly a key in
81
+ the map.
82
+ - **TOK-12.** Only an exact key matches. `color.ink2.bogus` is not `color.ink2`: it is not a
83
+ token, so it falls through to the keyword rule and is the error `unknown value`. (Before
84
+ 0.16.3 the engine shortened the path from the right and used the first prefix that was a
85
+ key, which hid typos, see D-2.)
86
+
87
+ ## 4.5 Tokens across files
88
+
89
+ A page usually loads several DSSL files. The host keeps a *registry* of token values and a
90
+ registry of motions (7.3) and passes both into the resolver for each new file, in load order.
91
+ The registry starts as the built-in defaults.
92
+
93
+ - **TOK-13.** For a file, the starting map is the registry. The resolver adds the file's own
94
+ tokens on top. After the file, the host updates the registry from the file's final map.
95
+ - **TOK-14.** The built-in defaults are in appendix A. They are defined with ordinary token
96
+ syntax and can be replaced by any `tokens` block.
97
+
98
+ Why: this is how `tokens.dssl` can define a palette that every later file uses, and how a
99
+ later file can override one token from an earlier one.
100
+
101
+ ## 4.6 Scoped tokens
102
+
103
+ A custom property with a dotted name re-declares a token for the element it is on.
104
+
105
+ ```dssl
106
+ .danger { --color.accent: color.err }
107
+ ```
108
+
109
+ - **TOK-15.** A declaration whose property starts with `--` and contains a `.` is written
110
+ with the property `--dssl-` followed by the rest of the name with dots replaced by dashes
111
+ (`--color.accent` becomes `--dssl-color-accent`). The value is resolved with
112
+ `bareIdents: true`. Custom properties inherit, so every `var()` in the subtree sees the new
113
+ value.
114
+ - **TOK-16.** A custom property without a dot (`--brand`) is passed through with only its
115
+ value resolved.
116
+
117
+ ## 4.7 Token shortcuts
118
+
119
+ Three semantic properties take a token name as a word (chapter 6): `shadow:`, `radius:`.
120
+
121
+ - **TOK-17.** `shadow: sm|md|lg|xl` is `box-shadow: var(--dssl-shadow-<word>)` whether or not
122
+ that token exists. `shadow: none` is `box-shadow: none`. A word that matches
123
+ `[A-Za-z][\w-]*` and for which `shadow.<word>` is a key in the map becomes
124
+ `box-shadow: var(--dssl-shadow-<word>)`. Anything else is resolved as an ordinary value.
125
+ - **TOK-18.** `radius:` takes one to four words. Each of `sm`, `md`, `lg`, `full`, `xl` becomes
126
+ `var(--dssl-radius-<word>)` whether or not it exists. Any other word that matches
127
+ `[A-Za-z][\w-]*` and for which `radius.<word>` is a key becomes the same kind of reference.
128
+ Everything else is resolved as an ordinary value. The words are joined by spaces into
129
+ `border-radius`. When one of the five words is used and `radius.<word>` is not a key, the
130
+ reference is still written, and the warning is
131
+ `radius "<word>" is not defined, so it will not apply` with the hint
132
+ `add it with tokens { radius { <word>: 32 } }`. By default only `sm`, `md`, `lg` and `full`
133
+ exist, so `xl` warns until the author defines it (D-4).
native/spec/05-cascade.md ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 5. Cascade
2
+
3
+ The resolver flattens the nested blocks of a file into a flat list of *rules*. The cascade
4
+ then decides, per element, which declaration of each property wins. This chapter defines both.
5
+
6
+ ## 5.1 The rule record
7
+
8
+ A rule is a record with these fields.
9
+
10
+ | Field | Meaning |
11
+ |---|---|
12
+ | `id` | a number, unique inside the document, in creation order |
13
+ | `sel` | the full selector text, nesting already joined (5.3) |
14
+ | `media` | a list of normalized media conditions (5.5). All must hold. |
15
+ | `signals` | a list of signal conditions (5.9). All must hold. |
16
+ | `containers` | a list of container conditions (5.6). All must hold. |
17
+ | `layer` | one of `tokens`, `base`, `components`, `utilities`, `pin` |
18
+ | `order` | a number: this rule's place in the file, counted from 0 (5.4) |
19
+ | `decls` | a list of declarations: `{ prop, value, line }` plus extras |
20
+ | `file`, `line` | where the rule came from |
21
+ | `pseudo` | the pseudo-element name for a shadow rule, else null |
22
+ | `states` | the set of state words found in `sel` (5.7) |
23
+
24
+ A *shadow rule* has the same fields. It is a rule whose `sel` names a pseudo-element target
25
+ (5.8).
26
+
27
+ ## 5.2 Layers
28
+
29
+ - **CAS-1.** The layers are ranked `tokens` (0), `base` (1), `components` (2), `utilities`
30
+ (3), `pin` (4).
31
+ - **CAS-2.** Rules outside any layer block are in `components`. `@layer <name> { }` puts the
32
+ rules inside it in layer `<name>` when that is one of the five names, and in `components`
33
+ for any other name, with the warning `unknown layer "<name>", treated as "components"`. The
34
+ hint is the did-you-mean over the five names, or
35
+ `the layers are tokens, base, components, utilities, pin` when none is close. (D-3)
36
+ - **CAS-3.** `pin { }` puts its rules in layer `pin`. Declarations in `pin` behave as if
37
+ `!important`: they beat declarations from every other layer, and they beat inline styles
38
+ that the page itself wrote (5.11).
39
+ - **CAS-4.** The layer `tokens` is used only for token rules (chapter 4).
40
+ - **CAS-5.** There is no specificity. For two matching rules, a lower layer rank loses to a
41
+ higher one. Within a layer, the later `order` wins.
42
+
43
+ ```dssl
44
+ .a { color: red }
45
+ pin { .a { color: blue } }
46
+ @layer base { .a { color: green } }
47
+ ```
48
+
49
+ All three match. `base` is below `components`, so `red` beats `green`, and `pin` beats both.
50
+ The element is blue.
51
+
52
+ ## 5.3 Nesting
53
+
54
+ A selector block may contain declarations, other selector blocks, state blocks, pseudo-element
55
+ blocks, `@media`, `@container`, `@signal`, `@layer` and `pin` blocks, and `use` lines.
56
+
57
+ - **CAS-6.** Nested selectors are joined to the parent selector. If the child contains `&`,
58
+ each `&` is replaced by the parent selector. Otherwise the result is the parent, a space
59
+ and the child (a descendant selector). A block at the top level is used as written.
60
+ - **CAS-7.** Lists are split at top-level commas (not inside parentheses or brackets, so
61
+ `:is(.x, .y)` stays whole) on both sides. Every parent part is joined with every child part,
62
+ parent-major: for each parent part in order, each child part in order. The results are
63
+ joined with `, `. So `.a, .b { .c { } }` means `.a .c, .b .c`, `.a { .c, .d { } }` means
64
+ `.a .c, .a .d`, and `&` is replaced by each parent part in turn. With no commas on either
65
+ side the result is the plain join of CAS-6. (Before 0.16.3 the lists were joined as whole
66
+ text, giving `.a, .b .c`. That was D-7.)
67
+ - **CAS-8.** A nested block whose header is a single plain word that is close to a state word
68
+ but not equal to it (`hove`, `focs`) is still a selector, but the resolver reports a
69
+ warning: `"<word>" compiled as a descendant selector`, with the hint naming the state.
70
+ This applies only when the header is a bare word of letters, digits and dashes, and there is
71
+ a parent selector. "Close" is the did-you-mean rule (9.3). The warning never fires for a
72
+ word that is an HTML element name (`li`, `td`, `menu`, and the rest of the list in the
73
+ reference engine), because a nested element name is a descendant selector on purpose. (Before
74
+ 0.16.3 `li` was warned about, being two edits from `link`. That was D-8.)
75
+ - **CAS-9.** Conditions nest. `@media`, `@container` and `@signal` add one entry to the
76
+ `media`, `containers` or `signals` list of everything inside. A layer block replaces the
77
+ layer of everything inside.
78
+ - **CAS-10.** A state block adds `:<state>` to the parent selector (5.7). A pseudo-element
79
+ block makes a shadow rule (5.8).
80
+
81
+ ```dssl
82
+ .card {
83
+ background: color.card
84
+ hover { background: color.paper }
85
+ @media dark { background: color.ink }
86
+ &.on { background: color.accent }
87
+ }
88
+ ```
89
+
90
+ ## 5.4 Order
91
+
92
+ - **CAS-11.** Each rule, shadow rule and `raw` block gets the next order number at the moment
93
+ it is emitted. The registry rule (4.3) gets 0. Token rules get a number at the moment their
94
+ block is reached.
95
+ - **CAS-12.** A rule is emitted when its block ends, or when a nested block starts, if it has
96
+ declarations waiting. So a block with declarations before and after a nested block makes
97
+ two rules, one for each fragment, in source order:
98
+
99
+ ```dssl
100
+ .a {
101
+ color: red
102
+ hover { color: blue }
103
+ padding: 4
104
+ }
105
+ ```
106
+
107
+ This makes `.a { color }` (order 1), `.a:hover { color }` (order 2) and `.a { padding }`
108
+ (order 3). The hover rule beats the base rule because it comes later. Writing the hover block
109
+ before `color: red` would make `color: red` win while hovering.
110
+
111
+ - **CAS-13.** A nested block is fully walked, and its own rule is emitted, before the rest of
112
+ its parent. Blocks nested inside it are emitted first.
113
+ - **CAS-14.** A rule with no declarations after resolution is not emitted.
114
+ - **CAS-15.** Declarations that appear with no selector around them are the error
115
+ `declarations must live inside a selector`, at the line of the first (line 0 for the file
116
+ top level).
117
+ - **CAS-16.** `order` is local to a file. When the host loads several files, it MUST place
118
+ them in load order and give every rule one position in that single sequence: the numbers of
119
+ a later file continue after the largest number of the earlier one. A later file wins over
120
+ an earlier one for equal layers.
121
+
122
+ Why: before 0.8.2 each file counted from zero and the numbers were compared across files,
123
+ which made a later file lose to an earlier one for no visible reason.
124
+
125
+ ## 5.5 Media conditions
126
+
127
+ `@media <query>` adds one normalized query to `media`.
128
+
129
+ - **CAS-17.** The query is trimmed. An empty query is the error `@media needs a condition`
130
+ and the query `(min-width: 0)`.
131
+ - **CAS-18.** The single words `dark`, `light` and `reduced-motion` (any case) become
132
+ `(prefers-color-scheme: dark)`, `(prefers-color-scheme: light)` and
133
+ `(prefers-reduced-motion: reduce)`.
134
+ - **CAS-19.** `N` or `>= N` becomes `(min-width: Npx)`. `<= N` becomes `(max-width: Npx)`.
135
+ `A..B` becomes `(min-width: Apx) and (max-width: Bpx)`. `N`, `A` and `B` are numbers.
136
+ - **CAS-20.** Any other query is kept, except that in any comparison, a bare number after
137
+ `>`, `<` or `=` that has no unit gets `px`: `(width >= 768)` becomes `(width >= 768px)`.
138
+ - **CAS-21.** A condition holds when the host's media query evaluation says it holds.
139
+
140
+ ```dssl
141
+ .a {
142
+ @media 768 { padding: 24 }
143
+ @media <= 480 { padding: 12 }
144
+ @media 480..960 { gap: 4 }
145
+ }
146
+ ```
147
+
148
+ ## 5.6 Container conditions
149
+
150
+ - **CAS-22.** `@container <query>` adds one normalized query (same rules as 5.5, with the
151
+ empty case `(min-width: 0)`) to `containers`.
152
+ - **CAS-23.** A container condition holds for an element when the nearest ancestor (or the
153
+ element itself) that the page marked as a container has a content width that satisfies every
154
+ bound in the query. A bound is `(min-width: N)`, `(max-width: N)`, `(width >= N)` or
155
+ `(width <= N)`. If no ancestor is a container, the condition does not hold.
156
+
157
+ ## 5.7 States
158
+
159
+ - **CAS-24.** A state block `name { ... }` in a block with selector `S` makes a rule with
160
+ selector `S:name`. When `S` is a list, the state is added to every part (`.a, .b` with
161
+ `hover` is `.a:hover, .b:hover`). The state words are in appendix A. State blocks can nest
162
+ (`hover { focus { } }` gives `S:hover:focus`).
163
+ - **CAS-25.** A state block with no selector around it is the error `"<name>" needs a
164
+ selector around it`.
165
+ - **CAS-26.** All state words except `seen` are the CSS pseudo-classes of the same name and
166
+ are matched by the host.
167
+ - **CAS-27.** `seen` is not a CSS pseudo-class. An element is *seen* once at least part of it
168
+ has been inside the viewport, and stays seen. A rule whose selector contains `:seen`
169
+ matches when the selector with `:seen` removed (or `*` if nothing is left) matches and the
170
+ element is seen. An implementation MUST re-evaluate the element when it becomes seen.
171
+ - **CAS-28.** Inside a `seen` block, a value that contains `->` is the error
172
+ `"<prop>: <value>" - the -> arrow is motion syntax and does nothing inside seen { }`. The
173
+ rule is still emitted.
174
+ - **CAS-29.** `states` is the set of state words found in `sel` as `:word`, from the list:
175
+ hover, focus-visible, focus, active, disabled, checked, empty, target, invalid, valid,
176
+ required, open, closed, read-only, read-write, placeholder-shown, first-child, last-child,
177
+ only-child, seen.
178
+
179
+ ## 5.8 Pseudo-elements and shadow rules
180
+
181
+ Inline styles cannot reach pseudo-elements, so the reference engine puts them in a separate
182
+ stylesheet. A native implementation has no such limit, but the resolver output still keeps
183
+ them in `shadowRules`, so that results are comparable.
184
+
185
+ - **CAS-30.** A block `::name { }` inside a selector block `S` makes a shadow rule with
186
+ `sel` = `S` and `pseudo` = `name`. When `S` is a list, there is one shadow rule per part
187
+ (and per state block inside it), each with its own copy of the declarations. At the top
188
+ level (no selector), `sel` is `*`.
189
+ - **CAS-31.** Inside a pseudo-element block, only declarations and state blocks are allowed.
190
+ Anything else is the error
191
+ `only declarations and states (hover { }) may appear inside a pseudo-element block`. A
192
+ state block inside makes a second shadow rule with `sel` = `S:<state>`. `seen` there is
193
+ the error `"seen" is engine-native and has no meaning on a pseudo-element`.
194
+ - **CAS-32.** A bare `content:` (no value) inside a pseudo-element becomes `content: ""`.
195
+ - **CAS-33.** Engine-native declarations (the `@` ones in 6.8) are not allowed on a
196
+ pseudo-element, except `@animate`. Each is the error
197
+ `"<prop>" is engine-native and has no meaning on ::<name> - a pseudo-element has no DOM
198
+ handle`, and is dropped.
199
+ - **CAS-34.** The same applies when the pseudo-element is written in the selector itself
200
+ (`.card::before { }`). The selector is split at the first top-level `::`. If every part of
201
+ a selector list has a pseudo-element, the rule becomes one shadow rule per part. If only
202
+ some do, the error is `selector list mixes pseudo-element and plain selectors: "<sel>"`
203
+ and nothing is emitted.
204
+ - **CAS-35.** A pseudo-element name MUST be in the pseudo-element list or match the vendor
205
+ pseudo pattern (appendix A), otherwise: `unknown pseudo-element "::<name>"`. `::slotted()`
206
+ is the error `::slotted() only exists inside a shadow tree, and DSSL styles the light DOM`.
207
+ `:seen` combined with a pseudo-element is an error.
208
+ - **CAS-36.** If every declaration of a shadow rule was rejected, nothing is emitted.
209
+
210
+ ```dssl
211
+ .btn {
212
+ ::after {
213
+ content: ""
214
+ opacity: 0
215
+ hover { opacity: 1 }
216
+ }
217
+ }
218
+ ```
219
+
220
+ ## 5.9 Signals
221
+
222
+ A signal is a named value that the page sets. It is how DSSL reads state that CSS cannot see
223
+ (a theme chosen in a menu, a game screen). Chapter 11 says how a browser could expose it.
224
+
225
+ - **CAS-37.** `@signal <expr>` adds one condition to `signals`. The forms are:
226
+
227
+ | Form | Condition record |
228
+ |---|---|
229
+ | `name` | `{ name, op: 'truthy', value: null }` |
230
+ | `name == value` | `{ name, op: '==', value }` |
231
+ | `name != value` | `{ name, op: '!=', value }` |
232
+ | `name in (a, b)` | `{ name, op: 'in', value: 'a,b', values: ['a','b'] }` |
233
+ | `name not in (a, b)` | `{ name, op: 'notin', value: 'a,b', values: ['a','b'] }` |
234
+
235
+ A name matches `[A-Za-z_][\w-]*`. A value may be quoted with `"` or `'`, and the quotes are
236
+ removed. List values are trimmed, unquoted, and empty ones removed.
237
+ - **CAS-38.** An expression in none of these forms is the error
238
+ `bad @signal expression "<expr>"` and is treated as `truthy` on the whole text. An empty
239
+ list is the error `@signal <name> <in|not in> ( ) has no values`.
240
+ - **CAS-39.** A signal's value is looked up on the element, then on each ancestor, then at the
241
+ document level. The first place that has the name wins. Values are compared as strings.
242
+ - **CAS-40.** A signal condition holds when:
243
+ - `truthy`: the name has a value that is not `false`, `null`, the string `false`, `0` or
244
+ the string `0`,
245
+ - `==`: the name has a value and its string equals the condition's value,
246
+ - `!=`: the name has no value, or its string differs,
247
+ - `in`: the name has a value and it is in the list,
248
+ - `notin`: the name has no value, or it is not in the list.
249
+
250
+ ## 5.10 Which rules apply to an element
251
+
252
+ - **CAS-41.** A rule applies to an element when all of these hold:
253
+ 1. its selector matches the element. A rule with selector `:root` applies only to the
254
+ document's root element;
255
+ 2. if the selector contains `:seen`, CAS-27 holds;
256
+ 3. every `media` condition holds;
257
+ 4. every `signals` condition holds;
258
+ 5. every `containers` condition holds.
259
+ - **CAS-42.** Applicable rules are sorted by layer rank, then by `order` (both ascending). The
260
+ declarations of the sorted rules are flattened in that order, each rule's declarations in
261
+ their written order.
262
+ - **CAS-43.** For each property, the last declaration in that sequence wins. Engine-native
263
+ declarations (6.8) each have their own combination rule, given with their feature.
264
+
265
+ ## 5.11 What else decides the value
266
+
267
+ These rules are about where DSSL meets the page.
268
+
269
+ - **CAS-44.** *Inline styles written by the page.* A declaration that the page itself put in
270
+ the element's `style` attribute, or set from a script, beats any rule that is not in `pin`.
271
+ A `pin` rule beats it. A property the page removes is free for rules again.
272
+ - **CAS-45.** *Shorthand and longhand.* A shorthand expands into its longhands in cascade
273
+ position. Of a shorthand and one of its longhands, the one that comes later in the
274
+ flattened sequence wins for that longhand. This holds for `border` and its `-color`,
275
+ `-width` and `-style` longhands, and for `padding`, `margin` and `gap` and their sides.
276
+ - **CAS-46.** *Handing back with yield.* After the sequence is flattened, properties named by
277
+ the last applicable `yield:` (6.7) are removed from the DSSL result, so ordinary CSS decides
278
+ them. `yield: none` clears the list.
279
+ - **CAS-47.** *Alignment makes a box.* If the winning declarations include any of
280
+ `place-items`, `place-content`, `align-items`, `justify-items`, `align-content` or
281
+ `justify-content`, and the element has no declared `display`, and the element's natural
282
+ `display` is not `flex`, `inline-flex`, `grid` or `inline-grid` (and is not `none` or
283
+ `contents`), then `display` is set to `inline-grid` when the natural `display` is `inline` or
284
+ starts with `inline-`, and to `grid` otherwise. An explicit `display` from any rule wins over
285
+ this.
286
+
287
+ Why: alignment properties do nothing on a plain `span` or `button`, and DSSL treats that as a
288
+ silent failure.
289
+
290
+ - **CAS-48.** *Border and positional shorthands in the inline lane.* A browser that computes
291
+ styles natively does not need extra rules for CAS-45: the CSS cascade already does it.
native/spec/06-properties.md ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 6. Properties
2
+
3
+ A declaration `prop: value` becomes zero or more *output declarations*. An output declaration
4
+ is either an ordinary CSS declaration `{ prop, value }`, or an *engine-native declaration*
5
+ whose property starts with `@` (6.8).
6
+
7
+ ## 6.1 Lookup order
8
+
9
+ - **PRP-1.** For each declaration, the resolver does the first of these that applies:
10
+ 1. If the value contains `!important` (VAL-15): report the error, emit nothing.
11
+ 2. If `prop` starts with `--`: it is a custom property (TOK-15, TOK-16).
12
+ 3. If `prop` is a semantic property (the names in 6.2 to 6.7, and appendix A): call that
13
+ property's expansion. It returns its own output declarations.
14
+ 4. If `prop` is `animation` or `animation-name`: resolve the value with `bareIdents: true`
15
+ and `unitless` per the unitless list. (Motion names are bare words, and must not be
16
+ reported as unknown values.)
17
+ 5. If the value is `fluid ...` and `prop` is in the fluid list: VAL-12 to VAL-14.
18
+ 6. If `prop` is not a known CSS property (appendix A): report
19
+ `unknown property "<prop>"`, hint = did-you-mean over the known properties and the
20
+ semantic property names. Emit nothing.
21
+ 7. Otherwise: one output declaration with the same property and the value resolved by
22
+ chapter 3.
23
+
24
+ The known-property list holds every property that CSS defines, plus a few vendor ones. It is
25
+ generated from the engine's vocabulary module (appendix A).
26
+
27
+ Each output declaration also carries the `line` of the source declaration.
28
+
29
+ ```dssl error
30
+ .a { colour: red }
31
+ ```
32
+
33
+ ## 6.2 Size and flow words
34
+
35
+ - **PRP-2.** `size: w [h]`. Two declarations `width` and `height`. The height defaults to the
36
+ width. Both are lengths.
37
+ - **PRP-3.** `circle: s`. `width: s`, `height: s`, `border-radius: 50%`.
38
+ - **PRP-4.** `z: n` is `z-index: n` (a plain number). `grow`, `shrink` are `flex-grow`,
39
+ `flex-shrink` (a plain number, default 1 when empty). `basis: l` is `flex-basis: l`.
40
+ - **PRP-5.** `cover [position]` is `position: <position or absolute>` and `inset: 0`.
41
+ `sticky [top]` is `position: sticky` and `top: <top or 0>`.
42
+ - **PRP-6.** `truncate` (no value) is `overflow: hidden`, `text-overflow: ellipsis`,
43
+ `white-space: nowrap`.
44
+ - **PRP-7.** `visually-hidden` (no value) is `position: absolute; width: 1px; height: 1px;
45
+ padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
46
+ border: 0`, in that order.
47
+
48
+ ## 6.3 Row, column, grid, stack, place, center
49
+
50
+ *Place words* map to two values, `(cross, main)`:
51
+
52
+ | word | cross | main |
53
+ |---|---|---|
54
+ | `center` | `center` | `center` |
55
+ | `start` | `start` | `flex-start` |
56
+ | `end` | `end` | `flex-end` |
57
+ | `between` | `center` | `space-between` |
58
+ | `around` | `center` | `space-around` |
59
+ | `evenly` | `center` | `space-evenly` |
60
+ | `stretch` | `stretch` | `flex-start` |
61
+ | `baseline` | `baseline` | `flex-start` |
62
+
63
+ - **PRP-8.** `row: words` and `column: words` start with `display: flex` and
64
+ `flex-direction: row|column`, then read the words in order:
65
+ - `gap N`: `gap: N` (a length). Missing `N` is the error `gap needs a value`.
66
+ - `wrap` / `nowrap`: `flex-wrap`.
67
+ - `reverse`: the direction becomes `row-reverse` or `column-reverse`.
68
+ - `inline`: `display` becomes `inline-flex`.
69
+ - a place word: see PRP-9.
70
+ - anything else: error `unknown word "<w>" in <row|column>`, hint = did-you-mean over
71
+ `gap center start end between around evenly stretch baseline wrap nowrap reverse inline`.
72
+ - **PRP-9.** Each place word has an axis role. `between`, `around`, `evenly` are *main*
73
+ (they write `justify-content`). `stretch` and `baseline` are *cross* (they write
74
+ `align-items`). `center`, `start`, `end` are *both*: they write whichever axes have not been
75
+ written yet by an earlier word in this declaration.
76
+ - A word that writes an axis a second time overrides it, with a warning:
77
+ `two words in <dir> write <prop>: "<new>" overrides "<old>"`, or when the same word, `"<w>"
78
+ is written twice in <dir> - the second copy wins`.
79
+ - A *both* word with both axes already taken writes nothing and warns
80
+ `"<w>" has nowhere to go: justify-content came from "<a>", align-items came from "<b>"`
81
+ (or the "written twice" form when it is the same word).
82
+ - **PRP-10.** `grid: words`. Words `gap N` and `rows N` are taken out first. The rest are the
83
+ column spec:
84
+ - nothing: error `grid needs columns`, columns are `none`,
85
+ - one integer `N`: `repeat(N, 1fr)`,
86
+ - `auto-fill L` or `auto-fit L`: `repeat(auto-fill, minmax(L, 1fr))` with `L` a length,
87
+ - otherwise each word is a length, joined by spaces.
88
+ The output is `display: grid`, `grid-template-columns`, then `gap` if given, then
89
+ `grid-template-rows` if given (`repeat(N, 1fr)` for an integer, a length otherwise).
90
+ - **PRP-11.** `stack: word` (default `center`) is `display: grid`,
91
+ `place-items: <cross> <main>` and `@stack: "1"`. An unknown word is the error
92
+ `unknown place "<w>" in stack` and only `@stack` is emitted.
93
+ - **PRP-12.** `place: word` is `place-items` and `place-content`, both `<cross> <main>`.
94
+ Unknown: `unknown place "<w>"`.
95
+ - **PRP-13.** `center: both|x|y` (default `both`). `both`: `place-items: center center` and
96
+ `place-content: center center`. `x`: `justify-items: center`, `justify-content: center`.
97
+ `y`: `align-items: center`, `align-content: center`. Unknown:
98
+ `unknown center axis "<v>"`.
99
+ - **PRP-14.** `align: center|start|end|stretch|baseline` is `align-items` with that word.
100
+ Unknown: `unknown align "<v>"`.
101
+
102
+ ```dssl
103
+ .toolbar { row: gap 8 between center }
104
+ .board { grid: 3 gap 12 }
105
+ .pane { stack: center }
106
+ .hero { center: both }
107
+ ```
108
+
109
+ The first rule gives `display: flex; flex-direction: row; gap: 8px;
110
+ justify-content: space-between; align-items: center`.
111
+
112
+ ## 6.4 Text and look
113
+
114
+ - **PRP-15.** `font: size[/line-height] [color]`. DSSL's `font:` is not the CSS shorthand.
115
+ It writes `font-size` (a length, default `16`), then `line-height` (a plain number) if a `/`
116
+ part is there, then `color` if more words follow.
117
+ If the value looks like the CSS shorthand (it contains `"`, `'` or `,`, or its first word is
118
+ one of `italic oblique bold bolder lighter normal small-caps condensed expanded caption icon
119
+ menu message-box small-caption status-bar system-ui serif sans-serif monospace`, or it is a
120
+ weight `100`..`900` followed by a sized word), the result is the error
121
+ `font: "<v>" looks like the CSS font shorthand, but DSSL's font: means size, line height and
122
+ colour` and nothing is emitted.
123
+ - **PRP-16.** `fluid: a, b[, v0, v1]` is `font-size` through VAL-12 to VAL-14.
124
+ - **PRP-17.** `shadow` and `radius`: TOK-17, TOK-18.
125
+ - **PRP-18.** `text-stroke: width [color] [inside]`, or `none`. It writes
126
+ `-webkit-text-stroke: <width> <color>` (width in px if bare, color default `currentcolor`),
127
+ and `paint-order: stroke fill` unless `inside` is given. `none` writes
128
+ `-webkit-text-stroke: 0` and `paint-order: normal`. A missing or non-numeric width is the
129
+ error `text-stroke needs a width first`.
130
+ - **PRP-19.** `outline-shape: width [color]`, or `none`. It writes one `filter` made of four
131
+ `drop-shadow(x y 0 color)` terms for `(w,0)`, `(-w,0)`, `(0,w)`, `(0,-w)`. `none` is
132
+ `filter: none`. A missing width is the error `outline-shape needs a width first`.
133
+ - **PRP-20.** `rotate: v`. A bare number `n` is `n` degrees. `x|y|z n` and four numbers also
134
+ get `deg`. Anything else is resolved as a unitless value.
135
+
136
+ ## 6.5 transition
137
+
138
+ `transition: part, part, ...` or `none`. Each part is `duration [easing] [property]`.
139
+
140
+ - **PRP-21.** The duration is a number with an optional `ms` or `s` unit. A bare number is
141
+ milliseconds. The easing is an easing name (appendix A) or a `cubic-bezier(...)` or
142
+ `steps(...)` call. The property is a known CSS property, or one of the deform names
143
+ (`bend`, `bend-y`, `bend-up`, `fold`, `stretch`, which mean `transform`), and defaults to
144
+ `all`. The part becomes `<property> <duration> <easing>`, with the easing defaulting to
145
+ `ease`. Parts are joined by `, `.
146
+ - **PRP-22.** If a part does not start with a time but contains one (the CSS habit,
147
+ `color .2s ease`), the error is `transition: the CSS form (property first) is not DSSL`,
148
+ with a hint that rewrites the part in DSSL order. A part with no time at all is the error
149
+ `transition duration "<d>" is not a time`. An unknown property is
150
+ `unknown transition property "<p>"`. A part with an error is dropped. If no part survives,
151
+ nothing is emitted.
152
+
153
+ ## 6.6 gradient
154
+
155
+ `gradient: layer + layer + ...`. The first layer is on top. Each layer is a *kind sentence*
156
+ optionally followed by *layer options*.
157
+
158
+ - **PRP-23.** Layers are split on a top-level `+` that has white space on both sides.
159
+ - **PRP-24.** Layer options may follow the sentence, in any order, each at the end of the text:
160
+ `tile W [H]` (background size, bare numbers are px, H defaults to W), `offset X Y`
161
+ (background position) and one of `no-repeat`, `repeat-x`, `repeat-y`.
162
+ - **PRP-25.** If the layer (after options) starts with a CSS gradient function, `url(`,
163
+ `image-set(` or `-webkit-`, it is resolved as an ordinary value. Otherwise it is a sentence.
164
+ - **PRP-26.** A sentence is `[repeating] linear|radial|conic <head> <stops>` or
165
+ `mesh <base>, <blob>, ...`. The output is the CSS function `linear-gradient(...)`,
166
+ `radial-gradient(...)`, `conic-gradient(...)` (with `repeating-` first when asked).
167
+ - **PRP-27.** Stops are separated by `->` or by commas. Each is a color (a token, hex, named
168
+ color, color function, alpha form) with up to two trailing positions, or a lone position
169
+ (a midpoint hint). Bare numbers in positions are px (degrees for `conic`), except `0`
170
+ which stays `0`. A stop word that is a plain word but not a color name is the error
171
+ `gradient stop "<s>" is not a color`.
172
+ - **PRP-28.** A *linear* head is an angle (a bare number is degrees, `45deg` also works) or
173
+ `to <side> [<side>]`. Default `180deg`. A *radial* head is made of `circle`, `ellipse`,
174
+ position words, lengths and `at`. A *conic* head is `from <angle>` and/or `at <position>`.
175
+ Anything else is an error saying what is not part of the gradient. An interpolation space
176
+ (`in oklch`, `in hsl longer hue`, ...) may end the head.
177
+ - **PRP-29.** `mesh` takes a base color first (an item without `at`), then blobs
178
+ `color at X Y [size S]` (size defaults to 60%). Each blob is
179
+ `radial-gradient(circle farthest-side at X Y, color, transparent S)`. The base is
180
+ `linear-gradient(base, base)`. The blobs come first, the base last.
181
+ - **PRP-30.** Errors: `gradient needs a kind: ...`, `gradient needs at least two stops`,
182
+ `"<s>" is a midpoint, so it has to sit between two colours`, `a gradient stop is missing its
183
+ color`, `mesh needs a base colour and at least one blob`, `mesh item "<i>" needs a position`,
184
+ `mesh needs a base colour first (an item without "at")`.
185
+ - **PRP-31.** `gradient:` writes `background-image` marked as coming from a gradient, plus
186
+ `background-size`, `background-position` and `background-repeat` only when some layer set
187
+ one (with `auto`, `0 0` and `repeat` for the layers that did not).
188
+ - **PRP-32.** `gradient-text: layers` writes the same image, then `background-clip: text`,
189
+ `-webkit-background-clip: text`, `color: transparent`, `-webkit-text-fill-color: transparent`.
190
+ - **PRP-33.** `gradient-flow: duration [easing] [alternate|alternate-reverse]` or `none`
191
+ writes `@gradient-flow` with `{ duration (ms), easing, direction }`. `none` and an empty
192
+ value write `@gradient-flow: null`. The easing default is `linear` and the direction
193
+ default is `normal`.
194
+
195
+ ```dssl
196
+ .stripes { gradient: repeating linear 45, #fff 0 10, color.ink 10 20 }
197
+ .blend { gradient: linear 90 in oklch, #ff0055 -> #00d4ff }
198
+ .sheen { gradient: radial at top left, #fff/40 -> transparent + linear 135, #123 -> #456 }
199
+ ```
200
+
201
+ ## 6.7 yield
202
+
203
+ `yield: name name ...` hands properties back to ordinary CSS.
204
+
205
+ - **PRP-34.** The names are separated by spaces or commas and lowercased. Each MUST be a known
206
+ CSS property or a semantic property name, otherwise the error
207
+ `yield: "<n>" is not a CSS property` and nothing is emitted. An empty list is the error
208
+ `yield needs the properties to hand back`. `none` writes `@yield: null`. Otherwise it writes
209
+ `@yield` with the list of names.
210
+ - **PRP-35.** For a name that is a shorthand, its longhands are handed back too (`padding`
211
+ hands back `padding-top` and the rest).
212
+
213
+ ## 6.8 Engine-native declarations
214
+
215
+ Declarations whose property starts with `@` are not CSS. They describe behavior that needs
216
+ more than a computed style. Each is produced by exactly one DSSL property and consumed by the
217
+ feature it belongs to.
218
+
219
+ | Output property | Produced by | Value | Chapter |
220
+ |---|---|---|---|
221
+ | `@animate` | `animate:` | a spec record, or null | 7 |
222
+ | `@stagger` | `stagger:` | milliseconds or null | 7 |
223
+ | `@overlap` | `overlap:` | `allow`, `deny`, `auto` | 8.6 |
224
+ | `@stack` | `stack:` | `"1"` | 6.3 |
225
+ | `@bend`, `@bend-y`, `@bend-up`, `@fold` | `bend`, `bend-y`, `bend-up`, `fold` | degrees | 8.5 |
226
+ | `@stretch` | `stretch:` | `{ factor, axis }` | 8.5 |
227
+ | `@bend-around` | `bend-around:` | degrees | 8.5 |
228
+ | `@gradient-flow` | `gradient-flow:` | spec record or null | 6.6 |
229
+ | `@texture` | `texture:` | list of layers or null | 8.1 |
230
+ | `@aura-glow`, `@aura-particles` | the aura family | list of layers or null | 8.2 |
231
+ | `@slider` | `slider:` | spec record | 8.3 |
232
+ | `@tilt` | `tilt:` | spec record or null | 8.4 |
233
+ | `@yield` | `yield:` | list of names or null | 6.7 |
234
+
235
+ - **PRP-36.** Engine-native declarations are not CSS and a host MUST NOT pass them to the
236
+ style system as properties. A Level 3 implementation combines them with the rule for each
237
+ feature. The default combination is the CSS one: for each name, the last in cascade order
238
+ wins. Exceptions are `@animate` (all of them are kept, 7.2) and `@yield` (names accumulate,
239
+ 5.11).
240
+
241
+ ## 6.9 behavior and use
242
+
243
+ ```dssl
244
+ behavior focus-ring {
245
+ outline: 2 solid color.accent
246
+ outline-offset: 2
247
+ }
248
+
249
+ .btn { use focus-ring }
250
+ ```
251
+
252
+ - **PRP-37.** A top-level `behavior name { ... }` defines a named block of declarations and
253
+ nested blocks. A second block with the same name is the error `behavior "<n>" is defined
254
+ twice`. Behaviors are collected before the walk, so a behavior may be used above its
255
+ definition. `behavior` is allowed only at the top level.
256
+ - **PRP-38.** `use name` inside a block replays the behavior's children there, as if they had
257
+ been written in that place, with the same context. An unknown name is
258
+ `unknown behavior "<n>"` with a did-you-mean over the defined names. A behavior that uses
259
+ itself, directly or through others, is `behavior cycle involving "<n>"`.
260
+ - **PRP-39.** The declarations of a behavior are resolved at the place they are used, so
261
+ tokens, conditions and layers there apply to them.
native/spec/07-motion.md ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 7. Motion
2
+
3
+ DSSL separates *defining* a motion from *running* it. `motion` defines keyframes. `animate:`
4
+ runs one on an element. `seen` and states decide when.
5
+
6
+ ## 7.1 Defining a motion
7
+
8
+ ```dssl
9
+ motion rise-in {
10
+ from { opacity: 0; translate: 0 18 }
11
+ to { opacity: 1; translate: 0 0 }
12
+ }
13
+
14
+ motion pulse {
15
+ essential
16
+ 0% { opacity: 1 }
17
+ 50% { opacity: 0.6 }
18
+ 100% { opacity: 1 }
19
+ }
20
+ ```
21
+
22
+ - **MOT-1.** `motion <name> { ... }` is allowed only at the top level. A motion block holds
23
+ keyframe blocks and the word `essential`. Any other declaration is the error `"<prop>" is
24
+ not allowed inside motion`, with the hint `motions take from/to/at blocks and essential`.
25
+ - **MOT-2.** A keyframe block header is one of `from` (0), `to` (100), `at N%` or `N%` or
26
+ `N`, or a comma list of these (`0%, 100%`). Every offset MUST be between 0 and 100. A bad
27
+ header is the error `bad keyframe selector "<h>"`.
28
+ - **MOT-3.** `essential` (alone, or as `essential: true`) marks the motion as one that keeps
29
+ playing under `prefers-reduced-motion`.
30
+ - **MOT-4.** Declarations inside a keyframe are resolved like any other declaration
31
+ (chapter 6) with `prop` set to the source property. Custom properties are dropped. Each
32
+ output property is named in camelCase (`background-color` becomes `backgroundColor`).
33
+ - **MOT-5.** In a keyframe, the deform words (`bend`, `bend-y`, `bend-up`, `fold`, `stretch`)
34
+ are not kept as properties. They are composed into one `transform` (8.5), with an extra
35
+ `transformOrigin` for the edge-pinned ones, because there is no box yet when keyframes are
36
+ built. A deform value of 0 is a real reset.
37
+ - **MOT-6.** A list header makes one frame per offset with the same properties. The frames of a
38
+ motion are sorted by offset (a stable sort).
39
+ - **MOT-7.** The result is `{ essential: bool, frames: [{ offset, props }] }`. A motion block
40
+ is parsed once, the first time it is needed.
41
+
42
+ ## 7.2 Running a motion
43
+
44
+ `animate: part, part, ...` or `none`. Each part is `name [words]`.
45
+
46
+ - **MOT-8.** The spec record starts as
47
+ `{ motion: name, duration: 400, easing: smooth, fill: 'both', delay: 0 }`. `smooth` is the
48
+ `cubic-bezier(0.4, 0, 0.2, 1)` easing from appendix A.
49
+ - **MOT-9.** Words after the name are read in order:
50
+ - `delay N`, `jitter N`, `iterations N`: the next word is a number with an optional `ms` or
51
+ `s` unit (for `iterations` a plain count). Missing: error `<word> needs a number`.
52
+ - `essential`: sets `essentialHere`.
53
+ - a number with optional unit: the first word after the name is the duration (`s` means
54
+ seconds, otherwise ms); a later one is the delay.
55
+ - an easing name: sets the easing.
56
+ - `both`, `forwards`, `backwards`, `none`, `auto`: the fill mode.
57
+ - `infinite`: iterations is infinity.
58
+ - `alternate`, `alternate-reverse`, `reverse`, `normal`: the direction.
59
+ - anything else: error `unknown animate part "<w>"`, hint = did-you-mean over the easing
60
+ names and the words above.
61
+ - **MOT-10.** One part gives one `@animate` declaration. Several parts (a top-level comma)
62
+ give several, in order. `animate: none` gives `@animate: null`.
63
+ - **MOT-11.** After the whole file is walked, every `@animate` with a motion name is linked:
64
+ the motion is looked up in this file, then in the motions the host passed in (7.3). If
65
+ found, the spec gets `frames`, and `essential` is true when the motion or the spec says so.
66
+ If not found, the error is `unknown motion "<n>"`, with a did-you-mean over the file's
67
+ motions, or a hint showing how to define one, and the spec is marked `broken`.
68
+ - **MOT-12.** Several `@animate` declarations matched by one element all run, in cascade
69
+ order. `@animate: null` from a later rule cancels the ones before it.
70
+
71
+ ## 7.3 Motions across files
72
+
73
+ - **MOT-13.** The host passes the already-built motions of earlier files to the resolver (the
74
+ motion registry). A motion defined in an earlier file can be used by a later one, like a
75
+ token. A motion in the current file shadows one in the registry.
76
+ - **MOT-14.** After resolving, every motion in the file is built (not only the referenced
77
+ ones) and returned as `motionFrames`, for any consumer that wants all of them (the static
78
+ emitter, 11.4).
79
+
80
+ ## 7.4 The animation shorthand
81
+
82
+ - **MOT-15.** The plain CSS properties `animation` and `animation-name` pass through (PRP-1,
83
+ item 4), with a bare number meaning ms. If a rule has one whose words do not include the
84
+ name of any motion in this file or the registry, the warning is
85
+ `animation "<v>" references no motion defined in this sheet - it will not run`.
86
+ - **MOT-16.** `animation-delay`, `-duration`, `-iteration-count`, `-direction`, `-fill-mode`,
87
+ `-timing-function` and `-play-state` do nothing in a rule that has no `animation` or
88
+ `animation-name`. The warning is `<prop> does nothing here: this rule has no CSS animation
89
+ to tune`, with a hint to put the setting in the `animate:` line.
90
+
91
+ ## 7.5 stagger
92
+
93
+ `stagger: N` on a parent offsets the delay of each element child's animations by
94
+ `index * N` ms, where `index` is the child's position among the parent's element children.
95
+
96
+ - **MOT-17.** The value is a number with optional `ms` or `s`, or `none`. Anything else is the
97
+ error `stagger needs a duration like "60" or "120ms", or "none"`. It writes `@stagger` with
98
+ milliseconds, or `@stagger: null`.
99
+
100
+ ## 7.6 Reduced motion
101
+
102
+ - **MOT-18.** When the user prefers reduced motion, a motion plays only if it is `essential`
103
+ (by its own flag or by `essential` on the `animate:` line). Others do not run. Textures,
104
+ auras and tilt follow their own rules in chapter 8.
native/spec/08-effects.md ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 8. Effects
2
+
3
+ Effects are the parts of DSSL that CSS has no equivalent for, or only a clumsy one. Each effect
4
+ has two halves:
5
+
6
+ - a **description**: the text the author writes and the record the resolver produces from it.
7
+ This half is fully specified here and a Level 2 implementation MUST produce the same records.
8
+ - a **rendering contract**: what a Level 4 renderer must draw. Where the contract depends on
9
+ generator code that is not yet written down as prose, this chapter says so. Those parts are
10
+ *defined by the reference implementation* (`public/dssl/texture.js`, `aura.js`, `slider.js`)
11
+ until a prose definition replaces them. They are marked **Pending**.
12
+
13
+ All vocabulary tables (setting names, ranges, presets) are generated into appendix A.
14
+
15
+ ## 8.1 Texture
16
+
17
+ `texture:` lays a generated image on top of an element's own background.
18
+
19
+ ```dssl
20
+ .card { background: #c9a66b; texture: wood }
21
+ .sheet { background: linear-gradient(135deg, #ff7a00, #7a1fff); texture: grain intensity 0.6 }
22
+ .poster { background: #e9e2d0; texture: paper, scanlines intensity 0.1 }
23
+ ```
24
+
25
+ **Description.**
26
+
27
+ - **FX-1.** The value is `none`, or a comma-separated list of layers. `none` writes
28
+ `@texture: null`. An empty value is the error `texture needs a name`. Any error anywhere in
29
+ the value drops the whole declaration.
30
+ - **FX-2.** Each layer starts with a preset name or a generator name (appendix A). Otherwise
31
+ the error is `unknown texture "<w>"`, with a did-you-mean over presets and generators.
32
+ - **FX-3.** After the name come settings. A setting is a key from the texture settings table
33
+ (appendix A), followed by as many values as that key takes. A key has one *kind*:
34
+ `flag` (no value), `number`, `numbers` (several), `word` (one of a list) or `color`.
35
+ - A number outside the key's range is the error `texture "<key>" must be between <min> and
36
+ <max>`. A non-number is `texture setting "<key>" needs a number, got "<v>"`.
37
+ - A word not in the list is `unknown <key> "<w>"` with a did-you-mean.
38
+ - A missing value is `texture setting "<key>" needs a value` (or `N values`). A value that is
39
+ itself a setting key counts as missing.
40
+ - A color value is resolved by chapter 3 (tokens, alpha and all). A bad color drops the
41
+ declaration.
42
+ - An unknown key is `unknown texture setting "<key>"` with a did-you-mean.
43
+ - A key marked *optional* (`symmetrical`) may stand alone, and then takes its first listed
44
+ value.
45
+ - **FX-4.** A preset is a stored list of layers. Writing a preset name gives those layers.
46
+ Settings apply to every layer of the preset, except that `color` and `color2` apply only to
47
+ the first layer when the preset has several. A generator name gives one layer made of the
48
+ generator's defaults.
49
+ - **FX-5.** The output is `@texture` with the list of layer records, each being the preset or
50
+ generator layer with the author's overrides applied, and a `preset` field naming the preset
51
+ (null for a bare generator).
52
+
53
+ **Rendering contract.**
54
+
55
+ - **FX-6.** The texture is drawn above the element's own background and below its content. The
56
+ author's background (a color, a gradient, or several layers, written as `background` or
57
+ `background-image`) is never replaced, and removing the texture gives back exactly the
58
+ original.
59
+ - **FX-7.** A texture is deterministic. The same layer record, including `seed`, MUST give the
60
+ same pixels on every machine and every engine. The tile MUST repeat without a visible seam.
61
+ (Why: the reference implementation draws from `seed` and plain arithmetic only.)
62
+ - **FX-8.** `symmetrical` mirrors every other tile on the chosen axis (`x`, `y`, or both), so
63
+ the pattern repeats twice as far apart and each tile edge meets its own reflection. It applies
64
+ to textures made as images (noise, bump, rings, waves, hex) and is ignored by the pure
65
+ pattern generators (lines, dots, grid, checker, carbon).
66
+ - **FX-9.** `animate N` steps the texture every `N` ms. It stands still when the user prefers
67
+ reduced motion.
68
+ - **FX-10.** Colors given as tokens follow the token, including under a theme change.
69
+
70
+ **Pending.** The generators (`noise`, `bump`, `lines`, `dots`, `grid`, `checker`, `weave`,
71
+ `carbon`, `waves`, `hex`, `rings`) and the seeded random number generator are defined by
72
+ `texture.js`. A prose definition of each is the first item of the Level 4 work.
73
+
74
+ ## 8.2 Aura
75
+
76
+ An aura is light and particles around an element. There are no presets. Fire, snow and sparks
77
+ are written by the author, usually as a `behavior`.
78
+
79
+ **Description.**
80
+
81
+ - **FX-11.** The properties are `aura`, `aura-glow`, `aura-particle` (and `aura-particles`),
82
+ `glow-particle` (and `-particles`), `aura-glow-particle` (and `-particles`). They write two
83
+ slots, `@aura-glow` and `@aura-particles`. A property that does not touch a slot leaves it
84
+ out of its output.
85
+
86
+ | Property | Writes |
87
+ |---|---|
88
+ | `aura` | both slots, as `glow ..., particles ...` clauses; it replaces both |
89
+ | `aura-glow` | the glow slot |
90
+ | `aura-particle(s)` | the particles slot |
91
+ | `glow-particle(s)` | the particles slot, with every particle glowing |
92
+ | `aura-glow-particle(s)` | both slots, with shared settings; both a glow and particles are required |
93
+
94
+ - **FX-12.** `none` clears exactly the slots the property owns (`aura-glow: none` leaves the
95
+ particles alone). An empty value is the error `<prop> needs a value`. An empty list writes
96
+ `null` for the slot.
97
+ - **FX-13.** A value is a comma-separated list of layers. In `aura`, each layer starts with
98
+ `glow`, `particles` or `particle`, otherwise the error
99
+ `an aura layer starts with glow or particles, not "<w>"`.
100
+ - **FX-14.** Inside a layer, the first color (or `->`-joined color list) and the first number
101
+ are positional: for a glow they are the color and the radius, for particles they are the
102
+ colors and the count. Everything else is a named setting from the glow or particle table
103
+ (appendix A). A leftover or unknown word is an error, with a did-you-mean.
104
+ - **FX-15.** The output is `@aura-glow` and/or `@aura-particles`, each a list of layer records
105
+ or `null`.
106
+
107
+ **Rendering contract.** (From the reference implementation. All of it is **Pending** as prose.)
108
+
109
+ - **FX-16.** The glow is drawn only outside the element's box. `behind` does the same for
110
+ particles.
111
+ - **FX-17.** An element that is off screen, hidden, clipped away by an overflow container, or
112
+ removed emits nothing. When no aura is active, the renderer stops its loop.
113
+ - **FX-18.** At most 1800 particles are alive across the page. When requests exceed that, every
114
+ emitter gets a fair share.
115
+ - **FX-19.** Under reduced motion, glows stand still and particles are not emitted, unless the
116
+ layer says `essential`.
117
+ - **FX-20.** A state block can change the aura. Leaving the state lets live particles finish,
118
+ then the emitter goes.
119
+ - **FX-21.** `from thumb` makes particles leave a range slider's thumb at the current value.
120
+
121
+ ## 8.3 Slider
122
+
123
+ `slider:` styles `<input type="range">` from one description. Without it, every engine needs
124
+ its own vendor pseudo-elements.
125
+
126
+ ```dssl
127
+ input.volume {
128
+ slider: track 6 color.line radius full, fill color.accent,
129
+ thumb 24 color.paper, ring 3 color.accent, glow color.accent 14,
130
+ ticks 10 color.muted
131
+ }
132
+ ```
133
+
134
+ **Description.**
135
+
136
+ - **FX-22.** The value is a comma-separated list of clauses. A clause starts with one of
137
+ `track`, `fill`, `thumb`, `shape`, `ring`, `glow`, `ticks`, `graph`, `graph-height`,
138
+ `graph-gap`. An unknown head is `unknown slider setting "<w>"` with a did-you-mean. An empty
139
+ value is `slider needs a description`.
140
+ - **FX-23.** Every word after a clause's head MUST be used by that clause. A leftover is the
141
+ error `unexpected "<words>" in the <head> setting`, with a hint about the missing comma.
142
+ - **FX-24.** The default record is: track height 6, color `var(--dssl-color-line)`, radius
143
+ `full`; fill `var(--dssl-color-accent)`; thumb size 22, color `var(--dssl-color-accent)`;
144
+ shape `circle`; no ring, no glow, no ticks, no graph; graph height 40, graph gap 2. Clauses
145
+ change the record in order.
146
+ - **FX-25.** Clause forms and limits are in appendix A. The notable ones: `ticks` needs a count
147
+ of at least 2, `graph` needs 2 to 120 numbers each in 0 to 100, `graph-height` is at least 8,
148
+ `graph-gap` is not negative.
149
+ - **FX-26.** The output is `@slider` with the record. When the rule is emitted (5.4), the
150
+ resolver turns the record into CSS: base declarations on the element, put *first* in the rule
151
+ so the author's own declarations in the same rule win, plus one shadow rule per vendor
152
+ pseudo-element. That CSS is defined by `sliderCss`. **Pending.**
153
+
154
+ **Rendering contract.**
155
+
156
+ - **FX-27.** The fill and the graph bars follow the value, which CSS cannot know. The renderer
157
+ keeps `--dssl-p` (the value as 0 to 1) up to date on the input, for typing, dragging, arrow
158
+ keys and programmatic changes.
159
+ - **FX-28.** A native engine that styles the slider parts itself does not need the vendor
160
+ pseudo-elements. It MUST give the same appearance as the record describes.
161
+
162
+ ## 8.4 Glass and tilt
163
+
164
+ ```dssl
165
+ .pane {
166
+ glass: 18 saturate 1.6
167
+ tilt: 10 lift 1.04 glare
168
+ }
169
+ ```
170
+
171
+ **Glass (plain CSS).**
172
+
173
+ - **FX-29.** `glass: words` or `none`. `none` writes `backdrop-filter: none`. Otherwise the
174
+ words are: a number (blur in px, default 16, not negative), `saturate N` (default 1.5),
175
+ `bright N`, `tint <color>` or `tint none`, and a bare color word counts as a tint. A word
176
+ that is none of these is `unknown glass setting "<w>"`. A missing number is
177
+ `<word> needs a number`. A color is recognised as a bare tint when it starts with `#`, `rgb`,
178
+ `hsl`, `oklch`, `oklab` or `color`, contains `/`, looks like `name.key`, or is `white`,
179
+ `black` or `transparent`.
180
+ - **FX-30.** The output is `backdrop-filter: blur(Bpx) saturate(S)` followed by
181
+ ` brightness(R)` when given. Then `background-color: <tint>` unless the tint is `none`, where
182
+ the default tint is `color-mix(in srgb, currentColor 8%, transparent)`. The author's own
183
+ `background` written after `glass:` replaces the wash.
184
+
185
+ **Tilt (needs the pointer).**
186
+
187
+ - **FX-31.** `tilt: words` or `none`. The words are a number (max degrees, 0 to 45, default 8),
188
+ `lift N` (scale on hover, above 0), `perspective N` (px, at least 100, default 900),
189
+ `speed N` (0.02 to 1, default 0.18), `invert` and `glare`. The record is
190
+ `{ max, lift, perspective, speed, invert, glare }`. `none` writes `@tilt: null`. An empty value
191
+ is `tilt needs a number of degrees`. Out-of-range values are errors that name the range.
192
+ - **FX-32.** The renderer adds, after the element's own `transform`:
193
+ `perspective(<P>px) rotateX(var(--dssl-tx, 0deg)) rotateY(var(--dssl-ty, 0deg))
194
+ scale(var(--dssl-ts, 1))`, and updates the three variables from the pointer, so that moving
195
+ the pointer never triggers a restyle. With `glare`, it also keeps `--dssl-gx` and
196
+ `--dssl-gy` (0% to 100%) pointing at the pointer.
197
+ - **FX-33.** Tilt ignores touch, stays flat under reduced motion, and stops when the page is
198
+ frozen. It combines with `translate`, `scale` and `rotate`. It does not combine with `bend`
199
+ or `fold`, because both write `transform`.
200
+
201
+ ## 8.5 Deform
202
+
203
+ `bend`, `bend-y`, `bend-up`, `fold`, `stretch` and `bend-around` deform an element, or for
204
+ `bend-around` its children.
205
+
206
+ - **FX-34.** `bend`, `bend-y`, `bend-up`, `fold` take a bare number of degrees (or `Ndeg`).
207
+ Anything else is `<name> takes a bare number of degrees`. They write `@bend`, `@bend-y`,
208
+ `@bend-up`, `@fold` with the number.
209
+ - **FX-35.** `stretch: F [horizontal|vertical|x|y]` with `F` above 0. It writes `@stretch`
210
+ `{ factor, axis }`, axis default `horizontal`. A bad value is
211
+ `stretch needs a positive factor, ...`; `F <= 0` is `stretch: F would flatten the element to
212
+ nothing`.
213
+ - **FX-36.** `bend-around: D` is a bare number of degrees between -170 and 170, otherwise
214
+ `bend-around caps at ±170° ...`. It writes `@bend-around`.
215
+ - **FX-37.** *Composition.* The deform parts of an element combine into one transform string,
216
+ in this order, followed by the element's own transform (when it is not `none`). `d` is the
217
+ record of numbers, `h` the element height in px (0 when there is no box yet):
218
+ 1. if any of bend, bend-y, fold, bend-up is non-zero: `perspective(900px)`;
219
+ 2. if bend: `rotateX(<bend>deg)`; if bend-y: `rotateY(<bend-y>deg)`;
220
+ 3. if fold: with `h > 0`: `translateY(<-h/2>px) rotateX(<fold>deg) translateY(<h/2>px)`;
221
+ without a box: `rotateX(<fold>deg)` and the origin `50% 0%`;
222
+ 4. if bend-up: with `h > 0`: `translateY(<h/2>px) rotateX(<up>deg) translateY(<-h/2>px)`;
223
+ without a box: `rotateX(<up>deg)` and the origin `50% 100%`;
224
+ 5. if stretch is set and not 1: for the horizontal axis
225
+ `scaleX(<f>) scaleY(<1/f rounded to 4 places>)`, for vertical
226
+ `scaleY(<f>) scaleX(<1/f>)`.
227
+
228
+ Translations are rounded to 2 decimal places. The result is `none` when there are no parts.
229
+ In keyframes (7.1) there is no box, so the second form of fold and bend-up is used.
230
+ - **FX-38.** *Arc layout.* `bend-around: D` lays `n` children along an arc. It does nothing
231
+ when `n < 2`, the container has no width, or `|D|` is too small (`sin(|D|/2) < 0.0001`).
232
+ Otherwise let `half = |D|/2` in radians, `R = round2((width/2) / sin(half))`, and the angle of
233
+ child `i` (from 0) is `round2(-D/2 + D*i/(n-1))` degrees. `round2(v)` is
234
+ `round(v * 100) / 100`. A positive `D` makes a dome, negative a smile.
235
+
236
+ ## 8.6 Overlap
237
+
238
+ - **FX-39.** `overlap: allow|none|deny|auto`. It writes `@overlap` with `allow` (for `none` too),
239
+ `deny` or `auto`. Anything else is `unknown overlap policy "<v>"`. The value inherits down the
240
+ tree.
241
+ - **FX-40.** `deny`: the element yields out of any overlap with other elements. `auto`: it
242
+ yields only while its opacity is below 100%. `allow` is the default. How an element yields is
243
+ defined by the reference engine's `overlapNudge`. **Pending.**
244
+
245
+ ## 8.7 Fonts
246
+
247
+ `fonts { }` loads fonts. A font is a stylesheet link or an `@font-face`, and neither belongs
248
+ in a rule, so it has its own block.
249
+
250
+ ```dssl
251
+ fonts {
252
+ Fraunces: 500 700 800
253
+ "Hanken Grotesk": 400 700 italic
254
+ Inter: 100..900 display optional
255
+ Brand: from "/fonts/brand.woff2" 400
256
+ }
257
+ ```
258
+
259
+ - **FX-41.** The body is verbatim text (2.7). Block comments are blanked, and a `//` comment
260
+ that starts a line or follows white space is removed. Each remaining line has a trailing `;`
261
+ removed and is `Family: words`. The family is everything before the first colon outside
262
+ quotes, unquoted, with runs of white space collapsed to one space. A line with no colon is
263
+ `expected "Family: weights" in "<text>"`.
264
+ - **FX-42.** The words are: weights (`400`, or a range `100..900`, each from 1 to 1000, a range
265
+ rising), `italic`, `display <swap|block|fallback|optional|auto>`, `from "<file>"`. Anything
266
+ else is `unknown font setting "<w>"`.
267
+ - **FX-43.** Without `from`, the font is from Google Fonts. All Google families together make
268
+ one link:
269
+ `https://fonts.googleapis.com/css2?family=<parts>&display=<d>` where each part is
270
+ `Name:wght@400;700` (sorted by number, spaces in the name as `+`), or `Name:ital,wght@0,400;
271
+ 0,700;1,400;1,700` with italics (all weights, upright first), or `Name` alone for no weights,
272
+ or `Name:ital@0;1` for italics with no weights. The `display` is the last one given on any
273
+ Google line, default `swap`. The link records the line of the `fonts` block.
274
+ - **FX-44.** With `from`, the line makes an `@font-face` block (as a `raw` entry, so it goes into
275
+ the page stylesheet) with `font-family`, `src: url("<file>")` plus `format("woff2"|"woff"|
276
+ "truetype"|"opentype")` by extension, `font-weight` (a range written with a space),
277
+ `font-style` and `font-display` (default `swap`). A `from` line takes at most one weight.
278
+ - **FX-45.** Google's servers see every visitor who loads one of their fonts. Authors who need
279
+ to avoid that use `from`.
native/spec/09-diagnostics.md ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 9. Diagnostics
2
+
3
+ DSSL is loud. A mistake is an error with a place and a hint, not a silent no-op.
4
+
5
+ ## 9.1 Records
6
+
7
+ - **DIA-1.** A diagnostic is `{ file, line, message, hint? }`. `hint` is omitted (not null)
8
+ when there is none. Messages and hints are English text and are compared exactly by the
9
+ conformance tests, so an implementation MUST use the wording in this spec.
10
+ - **DIA-2.** *Errors* mean something was wrong and was dropped or replaced. A source is `ok`
11
+ when it has no errors. *Warnings* mean the source compiles and runs but is probably not what
12
+ the author meant. Warnings never change `ok`.
13
+ - **DIA-3.** A tool may offer a strict mode that treats warnings as failures
14
+ (`dssl check --strict`). That is a tool choice, not part of resolving.
15
+ - **DIA-4.** Errors from the parser come first, then errors from the resolver, each in the
16
+ order found.
17
+ - **DIA-5.** *Deduplication.* At the end of resolving, errors with the same file, line and
18
+ message are reduced to one, keeping the last occurrence, and the order of the others is
19
+ unchanged.
20
+ - **DIA-6.** Unknown words are errors, never noise. A word that is left over after a construct
21
+ has used what it understands is an error, not ignored.
22
+ - **DIA-7.** A lint or any other helper MUST NOT be able to break a compile. If it fails, its
23
+ warnings are dropped and everything else stands.
24
+
25
+ ## 9.2 Where each message is defined
26
+
27
+ Each message appears in the chapter that defines the construct it belongs to, in code font. The
28
+ conformance corpus (chapter 10) covers every message at least once. Appendix B is generated from
29
+ the reference implementation and lists every message template, so a gap between this spec and
30
+ the corpus shows up as a failing check.
31
+
32
+ ## 9.3 Did-you-mean
33
+
34
+ - **DIA-8.** `didYouMean(word, candidates)` returns the best candidate, or nothing:
35
+ 1. If the word or the candidate list is empty, return nothing.
36
+ 2. Lowercase the word. For each candidate in order, skipping empty ones, compute the
37
+ Levenshtein distance (insert, delete, substitute, each cost 1) between the lowercase word
38
+ and the lowercase text of the candidate.
39
+ 3. Keep the first candidate with the smallest distance. A later candidate with an equal
40
+ distance does not replace it.
41
+ 4. The tolerance is `max(2, floor(length of the word / 3))`. If the smallest distance is
42
+ larger, return nothing. Otherwise return the candidate as text, in its original case.
43
+ - **DIA-9.** The order of the candidates is the order of the sets and lists in appendix A,
44
+ which is the insertion order in the reference implementation. A different order can change
45
+ which of two equally close words is suggested, so implementations MUST keep it.
46
+
47
+ ## 9.4 Warnings the resolver emits
48
+
49
+ | Where | Message starts with |
50
+ |---|---|
51
+ | 5.3, nested word near a state word | `"<w>" compiled as a descendant selector` |
52
+ | 6.3, row and column words | `two words in ...`, `"<w>" is written twice in ...`, `"<w>" has nowhere to go ...` |
53
+ | 6.6, `gradient:` and another image | `"<sel>" sets the background image twice` |
54
+ | 7.4, animation shorthand | `animation "<v>" references no motion ...`, `<prop> does nothing here ...` |
55
+ | 2.7, `raw { }` | `raw { } is verbatim CSS, so this line needs a ";" at the end` |
56
+ | same-rule display clash | `"<prop>:" expands to display: <v>, which overrides the earlier ...` |
57
+
58
+ - **DIA-10.** *Display clash.* Inside one rule, when a semantic property that expands to
59
+ `display` (`row`, `column`, `grid`, `stack`) comes after an explicit `display:` with a
60
+ different value, the warning is
61
+ `"<via>:" expands to display: <new>, which overrides the earlier "display: <old>" (line <n>)
62
+ in the same rule - later wins, so the earlier one is dead`. At most 400 warnings of this kind
63
+ are kept per file. Overrides across different rules are the cascade at work and get no
64
+ warning.
65
+ - **DIA-11.** *Raw semicolons.* In a `raw { }` body (comments blanked), a line that looks like
66
+ `name: value`, does not end in `;`, `{`, `}` or `,`, and is followed by another declaration
67
+ line or a line starting with `}`, is a warning at the line of that declaration inside the
68
+ block.
69
+
70
+ ## 9.5 Lint
71
+
72
+ A lint pass runs on the finished rules and adds warnings. It covers text contrast (WCAG ratio,
73
+ 4.5 for body text, 3 for large text), an outline removed from something you can tab to with no
74
+ `focus-visible` replacement, and an explicit width or height under 24px on a button or link. It
75
+ looks only at unconditional rules, follows tokens, alpha and `color-mix`, and is defined by
76
+ `lint.js`.
77
+
78
+ - **DIA-12.** A Level 2 implementation MAY omit the lint pass. It then declares `lint: false`,
79
+ and conformance tests compare warnings with the lint warnings removed. A lint warning is one
80
+ whose message starts with `text contrast is`, starts with `the focus outline is removed`, or
81
+ ends with `the smallest target WCAG 2.2 allows`.
native/spec/10-resolved-document.md ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 10. The resolved document
2
+
3
+ The resolver turns one source file into a *resolved document*. This chapter defines its exact
4
+ shape and how two implementations are compared. This is what makes Level 2 testable without a
5
+ browser.
6
+
7
+ ## 10.1 Inputs
8
+
9
+ - **DOC-1.** The resolver takes: the source text, the file name, the token registry (4.5) and
10
+ the motion registry (7.3). The registry defaults are the built-in tokens and no motions.
11
+
12
+ ## 10.2 The document
13
+
14
+ ```
15
+ {
16
+ errors: [ { file, line, message, hint? } ], // parser errors, then resolver errors
17
+ warnings: [ { file, line, message, hint? } ],
18
+ rules: [ Rule ],
19
+ shadowRules: [ Rule ],
20
+ raws: [ { css, file, line, order } ],
21
+ fontLinks: [ { url, line, file } ],
22
+ motionNames: [ name ], // motions defined in this file, in source order
23
+ motionFrames: { name: { essential, frames: [ { offset, props } ] } },
24
+ mediaQueries: [ query ], // distinct, in first-use order
25
+ tokens: { key: value }, // the final token map
26
+ registryRule: Rule
27
+ }
28
+ Rule = { id, sel, media, signals, containers, layer, order, decls, file, line, pseudo, states }
29
+ decl = { prop, value, line, via?, fromGradient? }
30
+ ```
31
+
32
+ - **DOC-2.** `rules` holds the ordinary rules and the token rules from `tokens` blocks, in the
33
+ order they were emitted. `shadowRules` holds pseudo-element rules (5.8). `raws` holds `raw`
34
+ and `fonts` font-face blocks in emission order. The registry rule is not in `rules`.
35
+ - **DOC-3.** `mediaQueries` lists every distinct entry of `media` found in `rules` and
36
+ `shadowRules`, in the order first seen, rules first.
37
+ - **DOC-4.** `tokens` is the final map of 4.3 (every key, with the last definition in source
38
+ order). Keys are compared as a set; the canonical form sorts them.
39
+ - **DOC-5.** A declaration may carry `via` (the semantic property that produced a `display`
40
+ value) and `fromGradient` (true for the `background-image` from `gradient:`). Both are part
41
+ of the record.
42
+ - **DOC-6.** `states` is a set of state words. It is written as a sorted array.
43
+ - **DOC-7.** `ok` is not stored. It is `errors.length == 0`.
44
+
45
+ ## 10.3 Canonical form
46
+
47
+ To compare two documents they are written as JSON in this form.
48
+
49
+ - **DOC-8.** Object keys are sorted by code point. Arrays keep their order, except `states`
50
+ and the `tokens` object, which are sorted.
51
+ - **DOC-9.** Numbers are written as the shortest decimal that reads back to the same
52
+ double, with no exponent for values between 1e-6 and 1e21, and no `-0`. Infinity is the
53
+ string `"Infinity"` (an `iterations` of `infinite`).
54
+ - **DOC-10.** A field that is `undefined` is omitted. A field that is `null` is `null`.
55
+ - **DOC-11.** Strings are compared as UTF-8 text.
56
+ - **DOC-12.** Two implementations are equal on a source when their canonical JSON is
57
+ byte-identical, after removing lint warnings from implementations that declared `lint: false`
58
+ (DIA-12).
59
+
60
+ ## 10.4 The corpus
61
+
62
+ The comparison needs inputs. The corpus is a folder of `.dssl` files:
63
+
64
+ - every stylesheet the site ships (`public/styles/*.dssl`),
65
+ - every fenced `dssl` block from the guide, the readme and this spec,
66
+ - the fixtures of the existing test suites,
67
+ - one file per diagnostic message, written to trigger it.
68
+
69
+ For each input, `scripts/native-dump.mjs` writes the canonical JSON of the reference engine's
70
+ output to `native/expected/`. The Rust test `native/tests/differential.rs` resolves the same
71
+ input and compares.
72
+
73
+ - **DOC-13.** The expected files are generated. They are committed so that a change in the
74
+ reference engine shows up in review as a change in expected output, and so the Rust tests run
75
+ without Node.
76
+ - **DOC-14.** A divergence (chapter 12) is excluded from the comparison by listing the input in
77
+ `native/corpus/EXCLUDED.md` with the divergence id. An excluded input is still run, and its
78
+ diff is printed, so a fix is noticed.
79
+ - **DOC-15.** Once the harness exists, `native/PARITY.md` lists, per chapter, how many corpus
80
+ inputs pass, fail and are excluded. It is generated by the test run and not written by hand.
81
+
82
+ ## 10.5 What the document does not contain
83
+
84
+ - The syntax tree (Level 1 compares it separately, 2.9).
85
+ - Behaviors (they are expanded during the walk and do not appear in the output).
86
+ - Anything about a particular element. Matching is stage 3 and needs a document.
native/spec/11-browser-integration.md ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 11. Browser integration
2
+
3
+ This chapter is informative. It says how a browser could host DSSL natively, stage by stage,
4
+ and which parts are cheap and which are real engineering. Nothing here is proposed to a
5
+ standards body, no browser implements it, and the reference crate has not been wired into any
6
+ engine. It is written so that a browser team can judge the size of the job.
7
+
8
+ ## 11.1 Delivery
9
+
10
+ DSSL needs a way to reach the parser, the way CSS has `<link rel="stylesheet">` and `<style>`.
11
+
12
+ - **INT-1.** A DSSL stylesheet is text with the media type `text/dssl`. (No registration is
13
+ proposed. This is a placeholder name.)
14
+ - **INT-2.** `<link rel="stylesheet" type="text/dssl" href="...">` and
15
+ `<style type="text/dssl">...</style>` load DSSL. A browser that does not know the type does
16
+ not load it, which is how CSS has always been introduced.
17
+ - **INT-3.** Several stylesheets are one cascade in load order (CAS-16), the same as CSS.
18
+ Tokens and motions flow from earlier files to later ones (4.5, 7.3) in that order.
19
+
20
+ Authors who need a page to work in a browser with no DSSL support can ship the output of
21
+ `dssl build` as CSS. That is the *static lane*. It is an author's choice, not a part of native
22
+ support, and it is described in 11.5.
23
+
24
+ ## 11.2 Where the parser sits
25
+
26
+ In every engine, a stylesheet becomes a list of rule objects, and nothing after that stage
27
+ cares what syntax the rules came from. A DSSL front end replaces the tokenizer and parser (CSS
28
+ stage 1) and produces the same kind of rule objects.
29
+
30
+ ```
31
+ CSS text --> CSS tokenizer + parser --\
32
+ >--> rule objects --> style resolution --> layout --> paint
33
+ DSSL text --> DSSL parser + resolver --/
34
+ ```
35
+
36
+ - **INT-4.** The resolved document (chapter 10) is the hand-off. Each rule record maps to an
37
+ engine rule: `sel` to the selector list, `decls` to the declaration block, `layer` to a
38
+ cascade layer, `media` and `containers` to conditional wrappers, and `order` to source order.
39
+ - **INT-5.** Engine-native declarations (`@...`, 6.8) are not properties. They go to the feature
40
+ that owns them (11.4), not to the style system's property table.
41
+
42
+ This is also how new CSS syntax has entered engines before. Nesting and cascade layers are
43
+ parsed into the same rule tree as everything else.
44
+
45
+ ## 11.3 The part that lowers to CSS
46
+
47
+ A large part of DSSL is already expressible in CSS. The proof is `dssl build`, which does exactly
48
+ that. Here is real output, from the reference engine, for a small source:
49
+
50
+ ```dssl
51
+ tokens { color { brand: #c2185b } }
52
+ .card {
53
+ background: color.brand
54
+ padding: 16
55
+ row: gap 8 between center
56
+ radius: md
57
+ hover { background: color.brand/80 }
58
+ @media dark { background: #181310 }
59
+ }
60
+ ```
61
+
62
+ compiles to the following (reformatted, and without the built-in token declarations):
63
+
64
+ ```css
65
+ @layer tokens, base, components, utilities, pin;
66
+
67
+ @layer tokens {
68
+ :where(:root) { --dssl-color-brand: #c2185b; }
69
+ }
70
+
71
+ @layer components {
72
+ :where(.card) {
73
+ background: var(--dssl-color-brand);
74
+ padding: 16px;
75
+ display: flex;
76
+ flex-direction: row;
77
+ gap: 8px;
78
+ justify-content: space-between;
79
+ align-items: center;
80
+ border-radius: var(--dssl-radius-md);
81
+ }
82
+ :where(.card:hover) {
83
+ background: color-mix(in oklab, var(--dssl-color-brand) 80%, transparent);
84
+ }
85
+ @media (prefers-color-scheme: dark) {
86
+ :where(.card) { background: #181310; }
87
+ }
88
+ }
89
+ ```
90
+
91
+ The mapping:
92
+
93
+ | DSSL | CSS it lowers to |
94
+ |---|---|
95
+ | tokens | custom properties on `:root`, inside `@layer tokens` |
96
+ | layers and `pin` | `@layer`, with `!important` for `pin` |
97
+ | no specificity | every selector wrapped in `:where()` so order alone decides |
98
+ | states | pseudo-classes |
99
+ | `@media`, `@container` | the same at-rules |
100
+ | semantic properties (`row`, `grid`, `radius`, `fluid`, ...) | longhand properties and `clamp()` |
101
+ | alpha (`color.ink/20`) | `color-mix(in oklab, ..., transparent)` |
102
+ | `motion` | `@keyframes` |
103
+ | `raw` | verbatim |
104
+ | `glass` | `backdrop-filter` and a background color |
105
+
106
+ - **INT-6.** A native engine MAY implement this part by lowering to its own CSS rule objects.
107
+ The result must equal the cascade of chapter 5. In particular, the `:where()` wrapping is how
108
+ CSS reproduces "no specificity", and the layer ranks reproduce CAS-1.
109
+
110
+ ## 11.4 The part that needs engine work
111
+
112
+ | Feature | What is missing from CSS | What a native engine adds |
113
+ |---|---|---|
114
+ | `@signal` | Named values set by the page, readable by the cascade | A page-visible store of named values, with ancestor lookup (CAS-39), invalidating style when one changes. The nearest CSS feature is a container style query on a custom property. Support differs between engines and must be checked before relying on it. |
115
+ | `seen` | A pseudo-class that matches once an element has been in view | An element flag set by the same machinery as intersection observation, and style invalidation when it flips (CAS-27). |
116
+ | `animate` | Per-element motion lists with `jitter`, `essential` and `stagger` | Mapping to the engine's keyframe animation, with a delay offset per child. `essential` and reduced motion are policy on top. |
117
+ | `texture` | A generated image layer that sits on the author's own background | A background image generator keyed by the layer record (FX-7), cached per tile. |
118
+ | `aura` | Glow and particles outside the box, over everything, with a particle budget | A page-level paint layer above content that does not take pointer events, with the budget of FX-18. |
119
+ | `slider` | One description for all engines' range-input parts | Direct styling of the control parts, plus a value-as-fraction the style can read (FX-27). |
120
+ | `tilt` | Pointer-driven 3D transform without restyling | A compositor-driven transform from pointer position (FX-32). |
121
+ | `bend`, `fold`, `bend-around` | Transforms that need the element's own box, or a container's children | These run during or after layout, because they need box sizes (FX-37, FX-38). |
122
+ | `overlap` | A rule that moves elements out of overlaps | Hook after layout (FX-40). This is the hardest item and is not specified yet. |
123
+ | alignment promotion | A box type chosen from an alignment word | Rule CAS-47 at computed-value time. |
124
+
125
+ - **INT-7.** For each row, the spec's description half (chapter 8) and corpus fixes what the
126
+ feature means. How the engine realises it is the engine's business, as long as the rendering
127
+ contract holds.
128
+
129
+ ## 11.5 The static lane and native support
130
+
131
+ `dssl build` already exists. Today it is the exit lane for pages that need no JavaScript. With
132
+ native support, the rows of 11.3 stop needing it, because the browser lowers them itself. What
133
+ stays on the author's side is what 11.4 lists and nothing else.
134
+
135
+ The build report already counts those leftovers (`@signal` rules, engine-native declarations),
136
+ so it is a ready measure of how much of a given page is the cheap part and how much is the
137
+ engine-work part.
138
+
139
+ ## 11.6 How a browser team would check their work
140
+
141
+ - Level 1 and 2: run the corpus (chapter 10). The expected JSON is committed. No browser
142
+ needed.
143
+ - Level 3: element-level tests, in the style of the web platform tests. A fixture is a small
144
+ HTML file, a DSSL file and the expected computed value of chosen properties on chosen
145
+ elements. These do not exist yet. They are the first thing to write after milestone 1.
146
+ - Level 4: reftests, comparing a rendering to a reference rendering, and pixel tests for the
147
+ effects (the repository already has pixel helpers in `scripts/lib/pixels.mjs`).
148
+
149
+ ## 11.7 What is not claimed
150
+
151
+ - No browser supports DSSL natively today, and this repository does not change that.
152
+ - The reference crate does not exist yet beyond a stub (see `native/README.md`). When it does,
153
+ it will parse and resolve. It will not match elements, compute styles or paint.
154
+ - The lowering table in 11.3 is the claim of the existing build lane, checked by the repository's
155
+ static parity test. It has not been run in browsers other than Chromium.
native/spec/12-known-divergences.md ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 12. Known divergences
2
+
3
+ Writing this spec from the reference engine and checking it against the engine turned up places
4
+ where the engine did not do what a careful reader of the language would expect. They were found
5
+ on 0.16.2. Seven are fixed in 0.16.3 and one is still open. This chapter keeps the record, so
6
+ that a second implementation knows which behavior is old and which is current.
7
+
8
+ | Id | Where | Summary | Status |
9
+ |---|---|---|---|
10
+ | D-1 | 2.2 | `//` inside an unquoted `url(...)` started a comment | fixed in 0.16.3 (SYN-6) |
11
+ | D-2 | 4.4 | A token path with a typo at the end resolved to its parent | fixed in 0.16.3 (TOK-12) |
12
+ | D-3 | 5.2 | An unknown `@layer` name was silently `components` | fixed in 0.16.3, now warns (CAS-2) |
13
+ | D-4 | 4.7 | `radius: xl` pointed at a token that does not exist | fixed in 0.16.3, now warns (TOK-18) |
14
+ | D-5 | 3.2 | A backslash did not escape a quote inside a value | fixed in 0.16.3 (VAL rule 2) |
15
+ | D-6 | 4.3 | The registry rule carries the last definition of a token | open, needs a browser test |
16
+ | D-7 | 5.3 | Nesting under a selector list joined the wrong way | fixed in 0.16.3 (CAS-7, CAS-24, CAS-30) |
17
+ | D-8 | 5.3 | A nested `li` got a "did you mean link" warning | fixed in 0.16.3 (CAS-8) |
18
+
19
+ A conformance corpus built from 0.16.3 or later has no exclusions for D-1 to D-5, D-7 and D-8.
20
+ Inputs produced by an older engine do.
21
+
22
+ ## D-1: `//` in an unquoted url (fixed)
23
+
24
+ ```dssl
25
+ .a { background: url(http://x.com/a.png) }
26
+ ```
27
+
28
+ The comment stripper did not know about `url( )`, so `//x.com/a.png) }` was removed as a line
29
+ comment. The value became `url(http` and the file reported one unclosed block. Inside `raw { }`
30
+ the closing brace of the `raw` block was eaten too. Now an unquoted `url(` ... `)` is copied as
31
+ written, and a comment right after it still works.
32
+
33
+ ## D-2: token path prefix match (fixed)
34
+
35
+ ```dssl error
36
+ tokens { color { ink2: #111 } }
37
+ .a { color: color.ink2.bogus }
38
+ ```
39
+
40
+ This used to resolve to `var(--dssl-color-ink2)` with no error, because the lookup shortened the
41
+ dotted path from the right and used the first prefix that was a token. A typo at the end of a
42
+ path is now the loud `unknown value` error, with a did-you-mean.
43
+
44
+ ## D-3 and D-4: silent fallbacks (fixed, now warnings)
45
+
46
+ `@layer weird { .a { color: red } }` puts the rule in `components`, and now says so. `radius: xl`
47
+ becomes `var(--dssl-radius-xl)`, and now warns when no `radius.xl` token exists, because by
48
+ default only `sm`, `md`, `lg` and `full` do. Both are warnings, so `ok` stays true and the output
49
+ is the same as before.
50
+
51
+ ## D-5: escaped quote in a value (fixed)
52
+
53
+ ```dssl
54
+ .a { content: "a\"b" }
55
+ ```
56
+
57
+ The parser's string scan honored the backslash, but the value resolver stopped the string at the
58
+ escaped quote and reported `unknown value "b"`. Strings in values, in function arguments and in
59
+ layer lists now skip the character after a backslash.
60
+
61
+ ## D-6: the registry rule (open)
62
+
63
+ The registry rule is built from the final token map (TOK-9). When a token is defined once at the
64
+ top level and again, later in the file, inside `@media dark`, the map holds the dark value, so
65
+ the registry rule carries it as an unconditional value. Within one file this does no harm,
66
+ because the registry rule has the lowest order and the file's own token rules come after it.
67
+ Whether it can override an earlier file's unconditional value depends on where the host installs
68
+ that bundle in the cascade, and that has not been tested.
69
+
70
+ Action: write a two-file test (a base palette, then a second file with a dark override) and run
71
+ it in a browser before deciding whether this is a bug.
72
+
73
+ ## D-7: nesting under a selector list (fixed)
74
+
75
+ ```dssl
76
+ .a, .b { .c { color: red } }
77
+ ```
78
+
79
+ used to produce `.a, .b .c`, which means `.a` and `.b .c`. The same applied to state blocks
80
+ (`.a, .b { hover { } }` gave `.a, .b:hover`, so only `.b` got the hover), to a child list
81
+ (`.a { .c, .d { } }` gave `.a .c, .d`) and to pseudo-element blocks. Parents and children are now
82
+ split at top-level commas and every parent part is joined with every child part, and a state is
83
+ added to every part. A selector with no commas is joined exactly as before.
84
+
85
+ ## D-8: false "did you mean" warning on element names (fixed)
86
+
87
+ ```dssl
88
+ .menu { li { color: red } }
89
+ ```
90
+
91
+ compiled correctly to `.menu li` and also warned that the author may have meant `link`, because
92
+ `li` is two edits from it. A nested word that is an HTML element name no longer warns. A real typo
93
+ such as `hove` still does.
native/spec/README.md ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DSSL specification
2
+
3
+ Status: working draft, written against the reference engine 0.16.3 (the bugs of chapter 12 were found on 0.16.2).
4
+ This is not a standard. It is the document a browser team (or anyone writing a second
5
+ implementation) would read to build DSSL without reading the JavaScript engine.
6
+
7
+ ## Why this exists
8
+
9
+ DSSL today is a JavaScript engine that writes inline styles. That is one way to run the
10
+ language. It is not the language. CSS is a language that browsers implement natively, and
11
+ this spec says what it would take to do the same for DSSL: what the text means, what every
12
+ stage produces, and what a correct implementation has to do.
13
+
14
+ The Rust crate next to this folder (`native/`) is the reference implementation of this spec
15
+ that does not use JavaScript. It is checked against the JavaScript engine by running both on
16
+ the same inputs and comparing the results.
17
+
18
+ ## Chapters
19
+
20
+ | # | Chapter | What it fixes |
21
+ |---|---|---|
22
+ | 1 | [Overview](01-overview.md) | The processing model, conformance levels, what CSS actually does |
23
+ | 2 | [Syntax](02-syntax.md) | Text to syntax tree: comments, blocks, declarations, raw |
24
+ | 3 | [Values](03-values.md) | Bare numbers, identifiers, functions, colors, alpha |
25
+ | 4 | [Tokens](04-tokens.md) | Design tokens and how they become custom properties |
26
+ | 5 | [Cascade](05-cascade.md) | Layers, order, states, media, containers, signals |
27
+ | 6 | [Properties](06-properties.md) | The semantic properties and how each one expands |
28
+ | 7 | [Motion](07-motion.md) | `motion`, `animate`, `seen`, `stagger` |
29
+ | 8 | [Effects](08-effects.md) | Texture, aura, slider, glass, tilt, deform, fonts |
30
+ | 9 | [Diagnostics](09-diagnostics.md) | Errors, warnings, did-you-mean, lint |
31
+ | 10 | [Resolved document](10-resolved-document.md) | The exact output a resolver produces, and how conformance is tested |
32
+ | 11 | [Browser integration](11-browser-integration.md) | How a browser would host this, stage by stage |
33
+ | 12 | [Known divergences](12-known-divergences.md) | Places where the JavaScript engine does not yet match this spec |
34
+
35
+ ## Conventions
36
+
37
+ - MUST, MUST NOT, SHOULD and MAY are used the way RFC 2119 uses them.
38
+ - Every testable rule has an id such as `SYN-4` or `CAS-7`. Conformance tests name the ids
39
+ they cover.
40
+ - Prose in the margin of a rule that starts with "Why:" is informative.
41
+ - Examples are executed. Every fenced `dssl` block is compiled by
42
+ `scripts/test-native-spec.mjs` against the JavaScript engine, and must produce no errors.
43
+ A block fenced as `dssl error` must produce at least one.
44
+ - The tables in the appendices are generated from the engine's own vocabulary modules by
45
+ `scripts/gen-native-vocab.mjs`. They are not edited by hand, so they cannot drift.
46
+
47
+ ## Normative and informative
48
+
49
+ Chapters 2 to 10 are normative. Chapters 1 and 11 are informative apart from the
50
+ conformance levels defined in 1.4. Chapter 12 is a list of bugs, and is informative.
51
+
52
+ Where this spec and the JavaScript engine disagree, chapter 12 says which one is wrong and
53
+ what the fix is. Until the engine is fixed, conformance tests for those cases are excluded
54
+ from the comparison and listed by id.
native/spec/appendix-a-vocabulary.md ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Appendix A: vocabulary
2
+
3
+ Generated by `scripts/gen-native-vocab.mjs` from the engine. Not edited by hand.
4
+ The full word lists, in the engine's own order, are in `native/data/vocabulary.json`.
5
+
6
+ ## A.1 State words
7
+
8
+ `hover`, `focus`, `focus-visible`, `active`, `disabled`, `checked`, `empty`, `target`, `required`, `invalid`, `valid`, `open`, `closed`, `placeholder-shown`, `read-only`, `read-write`, `enabled`, `indeterminate`, `first-child`, `last-child`, `only-child`, `first-of-type`, `last-of-type`, `link`, `visited`, `seen`
9
+
10
+ ## A.2 Pseudo-elements
11
+
12
+ `before`, `after`, `placeholder`, `selection`, `marker`, `first-line`, `first-letter`, `backdrop`, `file-selector-button`, `part`, `slotted`
13
+
14
+ A vendor pseudo-element name matches `^-(webkit|moz|ms)-`.
15
+
16
+ ## A.3 Unitless and time properties
17
+
18
+ Unitless (a bare number stays bare):
19
+
20
+ `opacity`, `line-height`, `z-index`, `font-weight`, `flex-grow`, `flex-shrink`, `flex`, `order`, `scale`, `widows`, `orphans`, `column-count`, `aspect-ratio`, `animation-iteration-count`, `flood-opacity`, `fill-opacity`, `stop-opacity`, `shape-image-threshold`, `counter-reset`, `counter-increment`, `counter-set`, `grid-row`, `grid-column`, `grid-area`, `font-feature-settings`, `font-variation-settings`, `tab-size`, `-webkit-line-clamp`, `line-clamp`, `stroke-miterlimit`, `stroke-opacity`, `initial-letter`, `font-size-adjust`
21
+
22
+ Time (a bare number is milliseconds):
23
+
24
+ `transition-delay`, `transition-duration`, `animation-delay`, `animation-duration`, `animation`
25
+
26
+ ## A.4 Functions
27
+
28
+ Unitless functions (numbers inside stay bare):
29
+
30
+ `oklch`, `oklab`, `lab`, `lch`, `hsl`, `hsla`, `rgb`, `rgba`, `hwb`, `color`, `color-mix`, `light-dark`, `brightness`, `saturate`, `contrast`, `grayscale`, `sepia`, `invert`, `opacity`, `scale`, `scalex`, `scaley`, `scalez`, `scale3d`, `steps`, `cubic-bezier`, `linear`, `radial`, `conic`
31
+
32
+ Length functions (bare numbers inside are px):
33
+
34
+ `calc`, `min`, `max`, `clamp`, `round`, `mod`, `rem`, `abs`, `sign`, `hypot`, `pow`, `sqrt`, `log`, `exp`, `blur`, `drop-shadow`
35
+
36
+ ## A.5 Fluid properties
37
+
38
+ `font-size`, `width`, `height`, `inline-size`, `block-size`, `min-width`, `max-width`, `min-height`, `max-height`, `gap`, `row-gap`, `column-gap`, `padding`, `margin`, `inset`, `top`, `right`, `bottom`, `left`, `padding-top`, `padding-right`, `padding-bottom`, `padding-left`, `padding-inline`, `padding-block`, `margin-top`, `margin-right`, `margin-bottom`, `margin-left`, `margin-inline`, `margin-block`, `letter-spacing`, `border-radius`
39
+
40
+ ## A.6 Place words
41
+
42
+ | word | cross | main |
43
+ |---|---|---|
44
+ | `center` | `center` | `center` |
45
+ | `start` | `start` | `flex-start` |
46
+ | `end` | `end` | `flex-end` |
47
+ | `between` | `center` | `space-between` |
48
+ | `around` | `center` | `space-around` |
49
+ | `evenly` | `center` | `space-evenly` |
50
+ | `stretch` | `stretch` | `flex-start` |
51
+ | `baseline` | `baseline` | `flex-start` |
52
+
53
+ ## A.7 Easings
54
+
55
+ | name | CSS |
56
+ |---|---|
57
+ | `snap` | `cubic-bezier(0.2, 0.9, 0.3, 1)` |
58
+ | `smooth` | `cubic-bezier(0.4, 0, 0.2, 1)` |
59
+ | `bounce` | `cubic-bezier(0.34, 1.56, 0.64, 1)` |
60
+ | `linear` | `linear` |
61
+ | `ease` | `ease` |
62
+ | `ease-in` | `ease-in` |
63
+ | `ease-out` | `ease-out` |
64
+ | `ease-in-out` | `ease-in-out` |
65
+
66
+ ## A.8 Built-in tokens
67
+
68
+ | key | value |
69
+ |---|---|
70
+ | `color.ink` | `#16161d` |
71
+ | `color.paper` | `#faf7f2` |
72
+ | `color.card` | `#ffffff` |
73
+ | `color.muted` | `#6f6a7a` |
74
+ | `color.line` | `#e5ded2` |
75
+ | `color.accent` | `oklch(62% 0.19 35)` |
76
+ | `color.accent-ink` | `#fff6ee` |
77
+ | `color.ok` | `#2e7d4f` |
78
+ | `color.err` | `#b3372e` |
79
+ | `space.1` | `4` |
80
+ | `space.2` | `8` |
81
+ | `space.3` | `12` |
82
+ | `space.4` | `16` |
83
+ | `space.5` | `24` |
84
+ | `space.6` | `32` |
85
+ | `space.7` | `48` |
86
+ | `space.8` | `64` |
87
+ | `radius.sm` | `8` |
88
+ | `radius.md` | `14` |
89
+ | `radius.lg` | `22` |
90
+ | `radius.full` | `999` |
91
+ | `shadow.sm` | `0 1 2 color.ink/8, 0 1 3 color.ink/6` |
92
+ | `shadow.md` | `0 2 4 color.ink/10, 0 4 12 color.ink/8` |
93
+ | `shadow.lg` | `0 4 8 color.ink/12, 0 12 32 color.ink/12` |
94
+ | `shadow.xl` | `0 8 16 color.ink/14, 0 24 64 color.ink/16` |
95
+ | `font.sans` | `system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` |
96
+ | `font.mono` | `ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace` |
97
+
98
+ ## A.9 Semantic properties
99
+
100
+ `size`, `row`, `column`, `grid`, `stack`, `place`, `center`, `cover`, `sticky`, `font`, `fluid`, `shadow`, `radius`, `circle`, `align`, `z`, `grow`, `shrink`, `basis`, `truncate`, `visually-hidden`, `transition`, `animate`, `stagger`, `overlap`, `bend`, `bend-y`, `bend-up`, `fold`, `bend-around`, `stretch`, `gradient`, `text-stroke`, `yield`, `outline-shape`, `rotate`, `gradient-text`, `gradient-flow`, `texture`, `aura`, `aura-glow`, `aura-particle`, `aura-particles`, `glow-particle`, `glow-particles`, `aura-glow-particle`, `aura-glow-particles`, `glass`, `tilt`, `slider`
101
+
102
+ ## A.10 Big sets
103
+
104
+ | set | count | where |
105
+ |---|---|---|
106
+ | CSS keywords | 1095 | `cssKeywords` in the data file |
107
+ | known CSS properties | 669 | `knownProps` |
108
+ | named colors | 148 | `namedColors` |
109
+
110
+ Color words allowed as gradient stops:
111
+
112
+ `black`, `silver`, `gray`, `white`, `maroon`, `red`, `purple`, `fuchsia`, `green`, `lime`, `olive`, `yellow`, `navy`, `blue`, `teal`, `aqua`, `orange`, `gold`, `pink`, `brown`, `violet`, `cyan`, `magenta`, `indigo`, `crimson`, `coral`, `tomato`, `goldenrod`, `khaki`, `slategray`, `slategrey`, `dimgray`, `dimgrey`, `lightgray`, `lightgrey`, `darkgray`, `darkgrey`, `gainsboro`, `whitesmoke`, `ghostwhite`, `ivory`, `beige`, `linen`, `seashell`, `snow`, `honeydew`, `mintcream`, `azure`, `aliceblue`, `lavender`, `canvas`, `canvastext`, `linktext`, `visitedtext`, `activetext`, `buttonface`, `buttontext`, `buttonborder`, `field`, `fieldtext`, `highlight`, `highlighttext`, `currentcolor`, `transparent`
113
+
114
+ ## A.11 Texture
115
+
116
+ Generators: `noise`, `bump`, `lines`, `dots`, `grid`, `checker`, `weave`, `carbon`, `waves`, `hex`, `rings`, `bricks`, `herringbone`, `plaid`, `scales`, `chevron`, `diamonds`, `triangles`, `starfield`, `terrazzo`
117
+
118
+ Blend modes: `normal`, `multiply`, `screen`, `overlay`, `soft-light`, `hard-light`, `color-burn`, `color-dodge`, `darken`, `lighten`, `difference`, `exclusion`
119
+
120
+ Presets (73): `grain`, `noise`, `rough`, `sand`, `paper`, `cloud`, `concrete`, `stone`, `crumpled`, `glitter`, `frost`, `marble`, `wood`, `oak`, `walnut`, `cherry`, `maple`, `pine`, `mahogany`, `ebony`, `birch`, `oak-planks`, `walnut-planks`, `bark`, `leather`, `brushed`, `canvas`, `linen`, `denim`, `carbon`, `scanlines`, `halftone`, `dots`, `lines`, `grid`, `checker`, `crosshatch`, `stripes`, `waves`, `hex`, `bricks`, `bricks-old`, `herringbone`, `plaid`, `tartan`, `gingham`, `scales`, `mermaid`, `shingles`, `chevron`, `argyle`, `lowpoly`, `tiles`, `blueprint`, `granite`, `slate`, `sandstone`, `terrazzo`, `confetti`, `rust`, `corrugated`, `cork`, `kraft`, `felt`, `burlap`, `stucco`, `starfield`, `snow`, `grass`, `water`, `rain`, `lava`, `topo`
121
+
122
+ Settings:
123
+
124
+ | key | kind | values | range | what it does |
125
+ |---|---|---|---|---|
126
+ | `scale` | number | 1 | 0.1 to 2000 | feature size in px: how big the grains, cells or lines are |
127
+ | `intensity` | number | 1 | 0 to 2 | how strong the layer is, 0 to 1 (more than 1 over-drives it) |
128
+ | `contrast` | number | 1 | 0 to 40 | noise contrast: 1 is as generated, higher is harsher |
129
+ | `threshold` | number | 1 | 0 to 1 | noise cut-off: only values above it show (glitter uses a high one) |
130
+ | `contour` | number | 1 | 0.005 to 0.5 | draw thin lines where the noise crosses the threshold, instead of a plain cut: veins, rings, contours |
131
+ | `octaves` | number | 1 | 1 to 8 | noise detail layers, 1 is smooth, 5 is rough |
132
+ | `seed` | number | 1 | 0 to 99999 | a different random pattern with the same look |
133
+ | `stretch` | numbers | 2 | 0.1 to 200 | stretch noise features along x and y, e.g. stretch 40 1 for wood grain |
134
+ | `depth` | number | 1 | 0 to 30 | bump height: how deep the relief looks |
135
+ | `light` | number | 1 | -360 to 360 | bump light direction in degrees |
136
+ | `angle` | number | 1 | -360 to 360 | direction of lines and patterns, in degrees |
137
+ | `thickness` | number | 1 | 0.1 to 500 | line or grid-line width in px |
138
+ | `radius` | number | 1 | 0.02 to 1 | dot size as a fraction of the cell (plaid: how wide the bands are as a fraction of the repeat) |
139
+ | `softness` | number | 1 | 0 to 1 | how soft the edges of dots and lines are |
140
+ | `animate` | number | 1 | 0 to 60000 | milliseconds per shimmer step (film-grain flicker); 0 is still |
141
+ | `type` | word | 1 | `fractal`, `turbulence` | noise flavour: fractal is clouds, turbulence is veins |
142
+ | `blend` | word | 1 | `normal`, `multiply`, `screen`, `overlay`, `soft-light`, `hard-light`, `color-burn`, `color-dodge`, `darken`, `lighten`, `difference`, `exclusion` | how the layer mixes with what is under it |
143
+ | `color` | color | 1 | | the main colour (dark end of noise, ink of lines and dots) |
144
+ | `color2` | color | 1 | | the second colour (light end of noise, background cell of patterns) |
145
+ | `figure` | number | 1 | 0 to 1 | rings generator: how strongly the growth rings arch into a cathedral figure, 0 is straight grain |
146
+ | `warp` | number | 1 | 0 to 1 | rings generator: how much each ring wobbles along its length |
147
+ | `pores` | number | 1 | 0 to 1 | rings generator: fine fibre and pore streaks between the rings |
148
+ | `knots` | number | 1 | 0 to 8 | rings generator: how many knots in one tile (the pattern repeats, so keep it small) |
149
+ | `planks` | number | 1 | 24 to 1200 | rings generator: lay it out as planks this many px tall, with a seam and a slightly different board each |
150
+ | `symmetrical` | word | 1 | `both`, `x`, `y` | mirror every other tile, so each edge meets its own reflection: no seam, and the repeat is twice as far apart. x mirrors left to right, y top to bottom, both (the default) does both. Works on the image-based textures (noise, bump, rings, waves, hex). |
151
+ | `density` | number | 1 | 0.05 to 20 | starfield and terrazzo: how many stars or chips in a tile, 1 is normal |
152
+ | `invert` | flag | 0 | | swap the two colours |
153
+
154
+ ## A.12 Aura
155
+
156
+ From words: `edge`, `outline`, `bottom`, `top`, `left`, `right`, `center`, `corners`, `thumb`
157
+
158
+ Shapes: `circle`, `spark`, `square`, `ring`, `star`, `flame`, `smoke`, `line`, `heart`, `diamond`, `triangle`, `plus`, `drop`, `bubble`, `snowflake`, `confetti`, `leaf`
159
+
160
+ Direction words: `up`, `down`, `left`, `right`, `out`, `in`
161
+
162
+ Blends: `add`, `normal`
163
+
164
+ Around shapes: `follow`, `box`, `round`, `pill`, `alpha`, `diamond`, `triangle`, `hexagon`, `octagon`, `pentagon`, `star`, `star4`, `star6`, `cross`, `heart`, `drop`, `bolt`
165
+
166
+ Glow settings:
167
+
168
+ | key | kind | range | what it does |
169
+ |---|---|---|---|
170
+ | `color` | color | | the light colour (default: the element's text colour) |
171
+ | `radius` | number | 1 to 400 | how far the glow reaches, in px |
172
+ | `intensity` | number | 0 to 4 | brightness, 1 is normal |
173
+ | `pulse` | number | 0 to 60000 | milliseconds for one breath in and out; 0 is steady |
174
+ | `depth` | number | 0 to 1 | how deep the pulse goes, 0 to 1 |
175
+ | `flicker` | number | 0 to 1 | random flicker, like a flame: 0 is steady, 1 is wild |
176
+ | `seed` | number | 0 to 99999 | a different random flicker with the same look |
177
+ | `texture` | texture | | texture(grain scale 2): break the light up with a texture |
178
+ | `essential` | flag | | keep animating even when the visitor prefers reduced motion |
179
+ | `unclipped` | flag | | let the light spill out of a parent that clips (overflow: hidden, auto, scroll). Without it the aura is cut at that edge, like the element itself |
180
+ | `around` | around | | the shape the light follows: follow (the default: the element's clip-path or rounded corners), box, round, pill, diamond, triangle, hexagon, octagon, pentagon, star, star4, star6, cross, heart, drop, bolt, alpha (an <img> or <canvas>'s visible pixels), or polygon(50% 0, 100% 50%, 50% 100%, 0 50%) |
181
+ | `inner` | flag | | light inside the edge (a rim) instead of outside it |
182
+ | `rays` | number | 1 to 64 | that many beams of light from the middle, slowly turning (see spin) |
183
+ | `spin` | number | -3600 to 3600 | rays turn this many degrees a second (default 10); on particles, how fast each one tumbles |
184
+ | `ripple` | number | 200 to 60000 | rings that spread out from the shape and fade, one every this many milliseconds |
185
+ | `nohalo` | flag | | only the rays and ripples, without the soft halo |
186
+ | `always` | flag | | keep emitting even when the element cannot be seen (opacity 0, display: none, visibility: hidden, on it or an ancestor): for an invisible anchor that smoke or sparks come out of |
187
+ | `z` | number | -1000000 to 2147483000 | the z-index of the layer this light is drawn on: `z 5` puts it under a hill with z-index 10. It meets elements in the page's root stacking context |
188
+
189
+ Glow defaults: `{"radius":24,"intensity":1,"pulse":0,"depth":0.35,"flicker":0,"seed":1}`
190
+
191
+ Particle settings:
192
+
193
+ | key | kind | range | what it does |
194
+ |---|---|---|---|
195
+ | `color` | colors | | colours over a particle's life: color #ff0 -> #f60 -> transparent |
196
+ | `count` | number | 1 to 600 | how many particles are alive at once |
197
+ | `size` | range | 0.5 to 300 | diameter in px: size 4, or size 10 4 (start and end of a random range) |
198
+ | `life` | range | 50 to 60000 | how long one lives, in ms: life 900, or life 600 1200 |
199
+ | `speed` | range | 0 to 3000 | px per second: speed 60, or speed 30 90 |
200
+ | `dir` | dir | | where they head: up, down, left, right, out, in, or degrees (-90 is up) |
201
+ | `spread` | number | 0 to 360 | how wide the fan of directions is, in degrees |
202
+ | `gravity` | number | -2000 to 2000 | px/s squared; negative pulls up (flames), positive down (snow) |
203
+ | `drag` | number | 0 to 10 | air resistance: how fast particles slow down |
204
+ | `turbulence` | number | 0 to 1000 | random sideways wobble, px/s |
205
+ | `from` | word | `edge`, `outline`, `bottom`, `top`, `left`, `right`, `center`, `corners`, `thumb` | where they are born: edge or outline (along the element's real outline, see around), bottom, top, left, right, center, corners, thumb (range sliders) |
206
+ | `shape` | word | `circle`, `spark`, `square`, `ring`, `star`, `flame`, `smoke`, `line`, `heart`, `diamond`, `triangle`, `plus`, `drop`, `bubble`, `snowflake`, `confetti`, `leaf` | circle, spark, square, ring, star, flame, smoke, line, heart, diamond, triangle, plus, drop, bubble, snowflake, confetti or leaf |
207
+ | `grow` | number | 0 to 10 | size at the end of life as a multiple of the start: 0.2 shrinks, 3 swells |
208
+ | `blend` | word | `add`, `normal` | add makes overlapping particles brighter, normal just stacks them |
209
+ | `glowing` | flag | | soft glowing particles that add light |
210
+ | `behind` | flag | | draw only outside the element's box, as if the element hid them |
211
+ | `seed` | number | 0 to 99999 | a different random pattern with the same look |
212
+ | `texture` | texture | | texture(grain): grainy particles |
213
+ | `essential` | flag | | keep animating even when the visitor prefers reduced motion |
214
+ | `unclipped` | flag | | let the particles leave a parent that clips (overflow: hidden, auto, scroll). Without it they are cut at that edge |
215
+ | `around` | around | | the shape `from edge` and `from outline` follow: follow (the default), box, round, pill, diamond, triangle, hexagon, octagon, pentagon, star, star4, star6, cross, heart, drop, bolt, or polygon(...) |
216
+ | `palette` | flag | | each particle takes ONE of the colours (confetti) instead of fading through them |
217
+ | `spin` | number | -3600 to 3600 | how fast each particle tumbles, in degrees a second (random direction) |
218
+ | `wind` | number | -3000 to 3000 | a sideways push, px/s squared; negative pushes left |
219
+ | `orbit` | number | -3600 to 3600 | the particles circle the middle of the element, this many degrees a second |
220
+ | `always` | flag | | keep emitting even when the element cannot be seen (opacity 0, display: none, visibility: hidden, on it or an ancestor): for an invisible anchor that smoke or sparks come out of |
221
+ | `z` | number | -1000000 to 2147483000 | the z-index of the layer these particles are drawn on: `z 5` puts them under a hill with z-index 10. It meets elements in the page's root stacking context |
222
+
223
+ Particle defaults: `{"count":40,"size":[4,4],"life":[900,900],"speed":[40,40],"dir":-90,"spread":30,"gravity":0,"drag":0,"turbulence":0,"from":"edge","shape":"circle","grow":0.3,"blend":null,"glowing":false,"seed":1}`
224
+
225
+ ## A.13 Slider
226
+
227
+ | clause | form |
228
+ |---|---|
229
+ | `track` | `track [height] [color] [radius <n>\|full]` |
230
+ | `fill` | `fill <color> \| fill none` |
231
+ | `thumb` | `thumb <size> [color]` |
232
+ | `shape` | `shape circle \| square \| diamond \| pill \| bar` |
233
+ | `ring` | `ring <width> <color>` |
234
+ | `glow` | `glow <color> <radius>` |
235
+ | `ticks` | `ticks <count> [color] [length]` |
236
+ | `graph` | `graph <v1> <v2> ... (each 0 to 100)` |
237
+ | `graph-height` | `graph-height <px>` |
238
+ | `graph-gap` | `graph-gap <px>` |
239
+
240
+ Thumb shapes: `circle`, `square`, `diamond`, `pill`, `bar`
241
+
242
+ ## A.14 Glass and tilt
243
+
244
+ | word | meaning |
245
+ |---|---|
246
+ | `blur` | a number: how many px to blur what is behind the element (default 16) |
247
+ | `saturate` | saturate <n>: colour boost for what shows through, 1 is unchanged (default 1.5) |
248
+ | `bright` | bright <n>: brightness of what shows through, 1 is unchanged |
249
+ | `tint` | tint <color> \| tint none: the milky layer on top (default: a faint wash of the text colour) |
250
+ | `max` | a number: the largest tilt in degrees (default 8) |
251
+ | `lift` | lift <n>: scale while the pointer is over it, e.g. 1.04 |
252
+ | `perspective` | perspective <px>: how strong the 3D depth looks (default 900) |
253
+ | `speed` | speed <0.05 to 1>: how quickly it follows (default 0.18) |
254
+ | `invert` | invert: tilt away from the pointer instead of toward it |
255
+ | `glare` | glare: also keep --dssl-gx and --dssl-gy (0% to 100%) pointing at the pointer, for your own highlight |
256
+
257
+ ## A.15 Fonts
258
+
259
+ | word | meaning |
260
+ |---|---|
261
+ | `weights` | a weight (400), several (400 700), or a variable range (100..900) |
262
+ | `italic` | also load the italic of each weight |
263
+ | `display` | display <swap\|block\|fallback\|optional>: how the text behaves while the font loads (default swap) |
264
+ | `from` | from "/path/to/file.woff2": use your own file instead of Google Fonts |
265
+
native/spec/appendix-b-messages.md ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Appendix B: message templates
2
+
3
+ Generated by `scripts/gen-native-vocab.mjs`. Every error and warning template found in the engine
4
+ source, by file and line. `${...}` marks a value filled in at run time. The extraction is by pattern,
5
+ so it can include a string that is not a message and can miss one built in an unusual way. It is a
6
+ checklist for the corpus, not the definition: the definition is in the chapters.
7
+
8
+ ## parser.js
9
+
10
+ | line | template |
11
+ |---|---|
12
+ | 177 | `unknown at-rule "${h.split(' ')[0]}"` |
13
+ | 183 | `unknown pseudo-element "::${name}"` |
14
+ | 205 | `unexpected "}"` |
15
+ | 224 | `block is missing a header before "{"` |
16
+ | 261 | `${meta.kind} { } must be top-level` |
17
+ | 282 | `the "${(meta.sel \|\| '').slice(0, 30) \|\| 'block'}" block was never closed with "}"` |
18
+ | 350 | `expected ":" in "${part.slice(0, 40)}"` |
19
+ | 356 | `declaration is missing a property name` |
20
+ | 367 | `"${prop}" is not a valid property name` |
21
+
22
+ ## resolver.js
23
+
24
+ | line | template |
25
+ |---|---|
26
+ | 415 | `${what} takes a bare number of degrees` |
27
+ | 500 | `mesh needs a base colour and at least one blob` |
28
+ | 504 | `mesh item "${it}" needs a position` |
29
+ | 509 | `mesh needs a base colour first (an item without "at")` |
30
+ | 521 | `gradient needs a kind: "linear", "radial", "conic" or "mesh" (any of them can be "repeating")` |
31
+ | 558 | `gradient needs at least two stops` |
32
+ | 576 | `"${head}" is not a linear gradient direction` |
33
+ | 587 | `"${a}" is not part of a radial gradient shape` |
34
+ | 602 | `"${head}" is not a conic gradient prefix` |
35
+ | 617 | `"${s}" is a midpoint, so it has to sit between two colours` |
36
+ | 626 | `a gradient stop is missing its color (got "${s}")` |
37
+ | 633 | `gradient stop "${s}" is not a color` |
38
+ | 666 | `gap needs a value` |
39
+ | 680 | `"${a}" is written twice in ${dir} - the second copy wins` |
40
+ | 681 | `two words in ${dir} write ${prop}: "${a}" overrides "${prev}"` |
41
+ | 706 | `unknown word "${a}" in ${dir}` |
42
+ | 728 | `${a} needs a number` |
43
+ | 745 | `unknown animate part "${a}"` |
44
+ | 773 | `grid needs columns` |
45
+ | 791 | `unknown place "${word}" in stack` |
46
+ | 799 | `unknown place "${v}"` |
47
+ | 819 | `unknown center axis "${v}"` |
48
+ | 840 | `font: "${v.trim()}" looks like the CSS font shorthand, but DSSL's font: means size, line height and colour` |
49
+ | 870 | `radius "${a}" is not defined, so it will not apply` |
50
+ | 891 | `unknown align "${v}"` |
51
+ | 947 | `transition: the CSS form (property first) is not DSSL` |
52
+ | 952 | `transition duration "${dur}" is not a time` |
53
+ | 968 | `unknown transition property "${raw}"` |
54
+ | 997 | `stagger needs a duration like "60" or "120ms", or "none"` |
55
+ | 1016 | `unknown overlap policy "${(v \|\| '').trim()}"` |
56
+ | 1040 | `bend-around caps at ±170° - past that the arc folds back on itself` |
57
+ | 1053 | `stretch needs a positive factor, like "stretch: 1.2" or "stretch: 1.4 vertical"` |
58
+ | 1062 | `stretch: ${m[1]} would flatten the element to nothing` |
59
+ | 1095 | `text-stroke needs a width first` |
60
+ | 1112 | `yield needs the properties to hand back` |
61
+ | 1115 | `yield: "${n}" is not a CSS property` |
62
+ | 1128 | `outline-shape needs a width first` |
63
+ | 1166 | `gradient-flow takes a duration like "6s" or "6000ms", or "none"` |
64
+ | 1179 | `unknown gradient-flow part "${a}"` |
65
+ | 1202 | `fluid: "${s}" ${why}` |
66
+ | 1216 | `${parts[0]} ${parts[1]}` |
67
+ | 1217 | `${parts[2]} ${parts[3]}` |
68
+ | 1306 | `!important does not exist in DSSL - "${prop}: ${String(value).replace(/\s*!\s*important\s*/i, '')}" was NOT applied` |
69
+ | 1346 | `unknown property "${prop}"` |
70
+ | 1374 | `@media needs a condition` |
71
+ | 1402 | `@signal ${list[1]} ${list[2]} ( ) has no values` |
72
+ | 1412 | `bad @signal expression "${expr}"` |
73
+ | 1451 | `unexpected "${child.kind}" inside tokens` |
74
+ | 1460 | `behavior "${node.name}" is defined twice` |
75
+ | 1503 | `"${child.prop}" is not allowed inside motion` |
76
+ | 1509 | `bad keyframe selector "${child.sel}"` |
77
+ | 1601 | `unknown behavior "${name}"` |
78
+ | 1604 | `behavior cycle involving "${name}"` |
79
+ | 1623 | `"${node.sel}" compiled as a descendant selector ("${combine(ctx.sel, node.sel)}")` |
80
+ | 1635 | `"${node.name}" needs a selector around it` |
81
+ | 1647 | `"${d.prop}: ${d.value}" - the -> arrow is motion syntax and does nothing inside seen { }` |
82
+ | 1688 | `unknown layer "${node.name}", treated as "components"` |
83
+ | 1710 | `"seen" is engine-native and has no meaning on a pseudo-element` |
84
+ | 1716 | `only declarations and states (hover { }) may appear inside a pseudo-element block` |
85
+ | 1725 | `"${d.prop}" is engine-native and has no meaning on ::${node.name} - a pseudo-element has no DOM handle` |
86
+ | 1750 | `tokens must be top-level or inside @media / @signal` |
87
+ | 1789 | `raw { } is verbatim CSS, so this line needs a ";" at the end ("${t.slice(0, 40)}")` |
88
+ | 1797 | `${node.kind} "${node.name}" must be top-level` |
89
+ | 1808 | `declarations must live inside a selector` |
90
+ | 1824 | `"${d.prop}" is engine-native and has no meaning on ::${ps.parts[0].pseudo.split(/[\s:(]/)[0]} - a pseudo-element has no DOM handle` |
91
+ | 1834 | `unknown pseudo-element "::${pseudo}"` |
92
+ | 1842 | `::slotted() only exists inside a shadow tree, and DSSL styles the light DOM` |
93
+ | 1850 | `the engine-native "seen" state can't combine with "::${name}" in shadow CSS` |
94
+ | 1868 | `selector list mixes pseudo-element and plain selectors: "${ctx.sel}"` |
95
+ | 1913 | `"${d.via}:" expands to display: ${d.value}, which overrides the earlier "display: ${explicit.value}" (line ${explicit.line}) in the same rule - later wins, so the earlier one is dead` |
96
+ | 1937 | `"${r.sel}" sets the background image twice (gradient: and ${other.prop}): the later one replaces the earlier, so one of them never shows` |
97
+ | 1953 | `${x.prop} does nothing here: this rule has no CSS animation to tune${usesAnimate ? ', it runs its motion through animate:' : ''}` |
98
+ | 1965 | `unknown motion "${d.value.motion}"` |
99
+ | 1984 | `animation "${d.value}" references no motion defined in this sheet - it will not run` |
100
+
101
+ ## texture.js
102
+
103
+ | line | template |
104
+ |---|---|
105
+ | 380 | `texture needs a name` |
106
+ | 390 | `unknown texture "${first}"` |
107
+ | 398 | `unknown texture setting "${key}"` |
108
+ | 406 | `texture setting "${key}" needs ${def.words === 1 ? 'a value' : def.words + ' values'}` |
109
+ | 412 | `texture setting "${key}" needs a number, got "${vals.join(' ')}"` |
110
+ | 413 | `texture "${key}" must be between ${def.min} and ${def.max}` |
111
+ | 417 | `unknown ${key} "${vals[0]}"` |
112
+
113
+ ## aura.js
114
+
115
+ | line | template |
116
+ |---|---|
117
+ | 84 | `unknown around "${w}"` |
118
+ | 89 | `polygon needs "x y" pairs in percent, got "${part.trim()}"` |
119
+ | 92 | `a polygon needs at least 3 points` |
120
+ | 325 | `${prop} needs a value` |
121
+ | 342 | `an aura layer starts with glow or particles, not "${head}"` |
122
+ | 377 | `unknown aura property "${prop}"` |
123
+
124
+ ## slider.js
125
+
126
+ | line | template |
127
+ |---|---|
128
+ | 69 | `slider needs a description` |
129
+ | 78 | `unknown slider setting "${head}"` |
130
+ | 92 | `track radius needs a number or "full"` |
131
+ | 102 | `thumb needs a size in px` |
132
+ | 107 | `unknown thumb shape "${rest[0] ?? ''}"` |
133
+ | 111 | `ring needs a width and a colour` |
134
+ | 116 | `glow needs a colour and a radius` |
135
+ | 121 | `ticks needs a count of at least 2` |
136
+ | 127 | `graph needs at least two numbers` |
137
+ | 128 | `graph values are 0 to 100` |
138
+ | 129 | `graph is limited to 120 bars` |
139
+ | 132 | `graph-height needs a number of px, at least 8` |
140
+ | 136 | `graph-gap needs a number of px` |
141
+ | 141 | `unexpected "${rest.slice(used).join(' ')}" in the ${head} setting` |
142
+
143
+ ## effects.js
144
+
145
+ | line | template |
146
+ |---|---|
147
+ | 43 | `${x} needs a number` |
148
+ | 49 | `tint needs a color or "none"` |
149
+ | 58 | `unknown glass setting "${x}"` |
150
+ | 60 | `glass blur cannot be negative` |
151
+ | 72 | `tilt needs a number of degrees` |
152
+ | 87 | `unknown tilt setting "${x}"` |
153
+ | 89 | `tilt degrees should be between 0 and 45` |
154
+ | 90 | `lift must be above 0` |
155
+ | 91 | `perspective must be at least 100px` |
156
+ | 92 | `speed must be between 0.02 and 1` |
157
+
158
+ ## fonts.js
159
+
160
+ | line | template |
161
+ |---|---|
162
+
163
+ ## lint.js
164
+
165
+ | line | template |
166
+ |---|---|
167
+ | 119 | `text contrast is ${ratio.toFixed(1)}:1 (${toHex(fgSolid)} on ${toHex(bg)}), below the ${need}:1 that readers need${large ? ' for large text' : ''}` |
168
+ | 147 | `${dim}: ${px}px on "${r.sel}" is below 24px, the smallest target WCAG 2.2 allows` |
169
+ | 160 | `the focus outline is removed from "${k.sel}" and nothing replaces it, so keyboard users lose their place` |
170
+ | 269 | `${p.a.props.join(', ')} in "${p.a.sel}" can never apply: "${p.b.sel}" (${p.b.file \|\| ''}${p.b.file ? ':' : ''}${p.b.line}) comes later, matches every element this rule matches, and sets ${p.a.props.length > 1 ? 'the same properties' : 'the same property'}` |
171
+
native/src/lib.rs ADDED
@@ -0,0 +1 @@
 
 
1
+ pub fn hello() -> &'static str { "dssl" }
native/src/main.rs ADDED
@@ -0,0 +1 @@
 
 
1
+ fn main() { println!("{}", dssl::hello()); }
package.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "dssl",
3
- "version": "0.16.2",
4
  "private": true,
5
  "description": "The DSSL style language and the website that is written in it.",
6
  "scripts": {
 
1
  {
2
  "name": "dssl",
3
+ "version": "0.16.3",
4
  "private": true,
5
  "description": "The DSSL style language and the website that is written in it.",
6
  "scripts": {
public/dssl/index.js CHANGED
@@ -19,7 +19,7 @@ import { resolve, DEFAULT_TOKENS, EASINGS } from './resolver.js';
19
  import { toCSS as docToCSS } from './static.js';
20
  import { DsslEngine } from './engine.js';
21
 
22
- export const VERSION = '0.16.2';
23
 
24
  let counter = 0;
25
  const loadedUrls = new Set(); // every .dssl file fetched by load(), so DSSL.watch() knows what to follow
 
19
  import { toCSS as docToCSS } from './static.js';
20
  import { DsslEngine } from './engine.js';
21
 
22
+ export const VERSION = '0.16.3';
23
 
24
  let counter = 0;
25
  const loadedUrls = new Set(); // every .dssl file fetched by load(), so DSSL.watch() knows what to follow
public/dssl/parser.js CHANGED
@@ -111,6 +111,17 @@ function stripComments(src) {
111
  continue;
112
  }
113
  if (c === '"' || c === "'") { q = c; out += c; continue; }
 
 
 
 
 
 
 
 
 
 
 
114
  if (c === '/' && n === '/') {
115
  // v0.7.1 bugfix: this handler used to emit its own '\n' AND rewind so
116
  // the main loop emitted the same newline again - every // comment
 
111
  continue;
112
  }
113
  if (c === '"' || c === "'") { q = c; out += c; continue; }
114
+ // An unquoted url(...) is one token: the // in http://x.com/a.png is not a comment. Copy it through the
115
+ // closing paren untouched (newlines included, so line numbers hold). A quoted url is handled by the string rule above.
116
+ if ((c === 'u' || c === 'U') && /^url\(/i.test(src.slice(i, i + 4)) && !/[\w-]/.test(src[i - 1] || '')) {
117
+ let k = i + 4;
118
+ while (k < src.length && /\s/.test(src[k])) k++;
119
+ if (src[k] !== '"' && src[k] !== "'") {
120
+ let e = k;
121
+ while (e < src.length && src[e] !== ')') e += src[e] === '\\' ? 2 : 1;
122
+ if (e < src.length) { out += src.slice(i, e + 1); i = e; continue; }
123
+ }
124
+ }
125
  if (c === '/' && n === '/') {
126
  // v0.7.1 bugfix: this handler used to emit its own '\n' AND rewind so
127
  // the main loop emitted the same newline again - every // comment
public/dssl/resolver.js CHANGED
@@ -182,6 +182,13 @@ const LENGTH_FN = new Set('calc,min,max,clamp,round,mod,rem,abs,sign,hypot,pow,s
182
  * - token references become var(--dssl-…), `token/NN` becomes color-mix
183
  * - unknown identifiers are errors (with did-you-mean)
184
  */
 
 
 
 
 
 
 
185
  /** split a selector list at top-level commas (not inside :is(...) and friends) */
186
  function splitTopLevel(sel) {
187
  const out = []; let depth = 0, cur = '';
@@ -209,7 +216,7 @@ function resolveValue(raw, ctx) {
209
 
210
  if (c === '"' || c === "'") {
211
  let j = i + 1;
212
- while (j < n && raw[j] !== c) j++;
213
  out += raw.slice(i, j + 1);
214
  i = j + 1;
215
  continue;
@@ -247,7 +254,7 @@ function resolveValue(raw, ctx) {
247
  if (raw[j] === ')') depth--;
248
  if (depth > 0 && (raw[j] === '"' || raw[j] === "'")) {
249
  const q = raw[j]; j++;
250
- while (j < n && raw[j] !== q) j++;
251
  }
252
  j++;
253
  }
@@ -386,16 +393,8 @@ function topSplit(str, sep) {
386
  }
387
 
388
  function findToken(tokens, ident) {
389
- if (tokens.has(ident)) return ident;
390
- // try progressive dot paths (a.b.c defined, user typed a.b.c - exact only, plus flat fallback)
391
- if (ident.includes('.')) {
392
- const parts = ident.split('.');
393
- for (let k = parts.length - 1; k > 0; k--) {
394
- const key = parts.slice(0, k).join('.');
395
- if (tokens.has(key)) return key; // hmm - partial match would change meaning; require exact
396
- }
397
- }
398
- return null;
399
  }
400
 
401
  /* ------------------------------------------------------------------ *
@@ -438,7 +437,7 @@ function splitLayers(s) {
438
  const out = []; let depth = 0, q = null, cur = '';
439
  for (let i = 0; i < s.length; i++) {
440
  const c = s[i];
441
- if (q) { cur += c; if (c === q) q = null; continue; }
442
  if (c === '"' || c === "'") { q = c; cur += c; continue; }
443
  if (c === '(' || c === '[') depth++;
444
  else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
@@ -866,7 +865,12 @@ const SEMANTIC = {
866
  radius(v, ctx) {
867
  const atoms = v.trim().split(/\s+/).filter(Boolean);
868
  const out = atoms.map(a => {
869
- if (['sm', 'md', 'lg', 'full', 'xl'].includes(a)) return `var(--dssl-radius-${a})`;
 
 
 
 
 
870
  if (/^[A-Za-z][\w-]*$/.test(a) && ctx.tokens && ctx.tokens.has('radius.' + a)) return `var(--dssl-radius-${a})`; // 0.14.0: radius { pill: 99 } then radius: pill
871
  return resolveValue(a, ctx);
872
  });
@@ -1614,7 +1618,7 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1614
  break;
1615
  }
1616
  case 'selector': {
1617
- if (/^[a-zA-Z][a-zA-Z0-9-]*$/.test(node.sel) && ctx.sel) {
1618
  const near = didYouMean(node.sel.toLowerCase(), [...STATE_WORDS]);
1619
  if (near) warnings.push({ file, line: node.line, message: `"${node.sel}" compiled as a descendant selector ("${combine(ctx.sel, node.sel)}")`, hint: `if you meant the state, it is spelled "${near}" - this block currently styles descendants called <${node.sel}>` });
1620
  }
@@ -1632,7 +1636,7 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1632
  break;
1633
  }
1634
  flushPending(node.line);
1635
- const childCtx = { ...ctx, sel: ctx.sel + ':' + node.name };
1636
  // v0.7.2 - the arrow is motion/gradient-stop syntax; inside a seen
1637
  // block it compiled into an inline style the browser rejects, so the
1638
  // element just sat at its base style forever. Say so, loudly, and
@@ -1678,7 +1682,11 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1678
  }
1679
  case 'layer':
1680
  case 'pin': {
1681
- const layer = node.kind === 'pin' ? 'pin' : (['tokens', 'base', 'components', 'utilities', 'pin'].includes(node.name) ? node.name : 'components');
 
 
 
 
1682
  flushPending(node.line);
1683
  const childCtx = { ...ctx, layer };
1684
  const sub = { decls: [] };
@@ -1688,6 +1696,7 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1688
  }
1689
  case 'pseudo': {
1690
  const base = ctx.sel || '*'; // top-level ::selection etc. means every element
 
1691
  const srule = {
1692
  id: ruleId++, sel: base, pseudo: node.name,
1693
  media: ctx.media, signals: ctx.signals, containers: ctx.containers,
@@ -1724,6 +1733,17 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1724
  fill(srule, node.children);
1725
  if (srule.decls.length) shadowRules.push(srule);
1726
  for (const st of stateRules) if (st.decls.length) shadowRules.push(st);
 
 
 
 
 
 
 
 
 
 
 
1727
  break;
1728
  }
1729
  case 'tokens': {
@@ -1994,10 +2014,24 @@ export function resolve(ast, file = 'inline.dssl', baseTokens = null, baseMotion
1994
  return { rules, shadowRules, raws, fontLinks, motions, motionFrames, errors, warnings, mediaQueries: [...mediaQueries], tokens: Object.fromEntries(tokenMap), registryRule };
1995
  }
1996
 
1997
- /** naive `&` substitution + descendant join */
 
1998
  function combine(parent, child) {
1999
- if (child.includes('&')) return child.split('&').join(parent);
2000
- return parent + ' ' + child;
 
 
 
 
 
 
 
 
 
 
 
 
 
2001
  }
2002
 
2003
  /* v0.5.0 - where does a selector carry a pseudo-element?
 
182
  * - token references become var(--dssl-…), `token/NN` becomes color-mix
183
  * - unknown identifiers are errors (with did-you-mean)
184
  */
185
+ /** HTML element names: a bare one nested in a rule is a descendant selector on purpose, never a mistyped state word */
186
+ const HTML_ELEMENTS = new Set(('a,abbr,acronym,address,area,article,aside,audio,b,base,bdi,bdo,big,blockquote,body,br,button,canvas,caption,center,cite,code,col,colgroup,' +
187
+ 'data,datalist,dd,del,details,dfn,dialog,dir,div,dl,dt,em,embed,fieldset,figcaption,figure,font,footer,form,frame,frameset,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,' +
188
+ 'i,iframe,img,input,ins,kbd,label,legend,li,link,main,map,mark,marquee,menu,meta,meter,nav,nobr,noscript,object,ol,optgroup,option,output,p,param,picture,pre,progress,q,' +
189
+ 'rp,rt,ruby,s,samp,script,search,section,select,slot,small,source,span,strike,strong,style,sub,summary,sup,table,tbody,td,template,textarea,tfoot,th,thead,time,title,tr,' +
190
+ 'track,tt,u,ul,var,video,wbr,svg,math').split(','));
191
+
192
  /** split a selector list at top-level commas (not inside :is(...) and friends) */
193
  function splitTopLevel(sel) {
194
  const out = []; let depth = 0, cur = '';
 
216
 
217
  if (c === '"' || c === "'") {
218
  let j = i + 1;
219
+ while (j < n && raw[j] !== c) j += raw[j] === '\\' ? 2 : 1; // a backslash escapes the next character
220
  out += raw.slice(i, j + 1);
221
  i = j + 1;
222
  continue;
 
254
  if (raw[j] === ')') depth--;
255
  if (depth > 0 && (raw[j] === '"' || raw[j] === "'")) {
256
  const q = raw[j]; j++;
257
+ while (j < n && raw[j] !== q) j += raw[j] === '\\' ? 2 : 1;
258
  }
259
  j++;
260
  }
 
393
  }
394
 
395
  function findToken(tokens, ident) {
396
+ // exact key only: color.ink2.bogus must not quietly become color.ink2, it falls through to "unknown value"
397
+ return tokens.has(ident) ? ident : null;
 
 
 
 
 
 
 
 
398
  }
399
 
400
  /* ------------------------------------------------------------------ *
 
437
  const out = []; let depth = 0, q = null, cur = '';
438
  for (let i = 0; i < s.length; i++) {
439
  const c = s[i];
440
+ if (q) { cur += c; if (c === q && s[i - 1] !== '\\') q = null; continue; }
441
  if (c === '"' || c === "'") { q = c; cur += c; continue; }
442
  if (c === '(' || c === '[') depth++;
443
  else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
 
865
  radius(v, ctx) {
866
  const atoms = v.trim().split(/\s+/).filter(Boolean);
867
  const out = atoms.map(a => {
868
+ if (['sm', 'md', 'lg', 'full', 'xl'].includes(a)) {
869
+ if (ctx.tokens && !ctx.tokens.has('radius.' + a) && ctx.warnings) {
870
+ ctx.warnings.push({ file: ctx.file, line: ctx.line, message: `radius "${a}" is not defined, so it will not apply`, hint: `add it with tokens { radius { ${a}: 32 } }` });
871
+ }
872
+ return `var(--dssl-radius-${a})`;
873
+ }
874
  if (/^[A-Za-z][\w-]*$/.test(a) && ctx.tokens && ctx.tokens.has('radius.' + a)) return `var(--dssl-radius-${a})`; // 0.14.0: radius { pill: 99 } then radius: pill
875
  return resolveValue(a, ctx);
876
  });
 
1618
  break;
1619
  }
1620
  case 'selector': {
1621
+ if (/^[a-zA-Z][a-zA-Z0-9-]*$/.test(node.sel) && ctx.sel && !HTML_ELEMENTS.has(node.sel.toLowerCase())) {
1622
  const near = didYouMean(node.sel.toLowerCase(), [...STATE_WORDS]);
1623
  if (near) warnings.push({ file, line: node.line, message: `"${node.sel}" compiled as a descendant selector ("${combine(ctx.sel, node.sel)}")`, hint: `if you meant the state, it is spelled "${near}" - this block currently styles descendants called <${node.sel}>` });
1624
  }
 
1636
  break;
1637
  }
1638
  flushPending(node.line);
1639
+ const childCtx = { ...ctx, sel: withState(ctx.sel, node.name) };
1640
  // v0.7.2 - the arrow is motion/gradient-stop syntax; inside a seen
1641
  // block it compiled into an inline style the browser rejects, so the
1642
  // element just sat at its base style forever. Say so, loudly, and
 
1682
  }
1683
  case 'layer':
1684
  case 'pin': {
1685
+ const LAYERS = ['tokens', 'base', 'components', 'utilities', 'pin'];
1686
+ const layer = node.kind === 'pin' ? 'pin' : (LAYERS.includes(node.name) ? node.name : 'components');
1687
+ if (node.kind === 'layer' && !LAYERS.includes(node.name)) {
1688
+ warnings.push({ file, line: node.line, message: `unknown layer "${node.name}", treated as "components"`, hint: didYouMean(node.name, LAYERS) || 'the layers are tokens, base, components, utilities, pin' });
1689
+ }
1690
  flushPending(node.line);
1691
  const childCtx = { ...ctx, layer };
1692
  const sub = { decls: [] };
 
1696
  }
1697
  case 'pseudo': {
1698
  const base = ctx.sel || '*'; // top-level ::selection etc. means every element
1699
+ const parts = splitTopLevel(base);
1700
  const srule = {
1701
  id: ruleId++, sel: base, pseudo: node.name,
1702
  media: ctx.media, signals: ctx.signals, containers: ctx.containers,
 
1733
  fill(srule, node.children);
1734
  if (srule.decls.length) shadowRules.push(srule);
1735
  for (const st of stateRules) if (st.decls.length) shadowRules.push(st);
1736
+ // a selector list in front of the block (.a, .b { ::before { } }): the rules above were built on the whole
1737
+ // list; give every further part its own copy so each gets its own pseudo-element rule
1738
+ if (parts.length > 1) {
1739
+ const built = [srule, ...stateRules].filter((r) => r.decls.length);
1740
+ for (const part of parts.slice(1)) {
1741
+ for (const r of built) {
1742
+ shadowRules.push({ ...r, decls: r.decls.map((d) => ({ ...d })), id: ruleId++, order: order++, sel: part + r.sel.slice(base.length) });
1743
+ }
1744
+ }
1745
+ for (const r of built) r.sel = parts[0] + r.sel.slice(base.length);
1746
+ }
1747
  break;
1748
  }
1749
  case 'tokens': {
 
2014
  return { rules, shadowRules, raws, fontLinks, motions, motionFrames, errors, warnings, mediaQueries: [...mediaQueries], tokens: Object.fromEntries(tokenMap), registryRule };
2015
  }
2016
 
2017
+ /** `&` substitution + descendant join. Lists are split at top-level commas and every parent part is joined with
2018
+ * every child part (parent-major), so `.a, .b { .c { } }` is `.a .c, .b .c`. Commas inside :is( ) or [ ] stay put. */
2019
  function combine(parent, child) {
2020
+ const pp = splitTopLevel(parent), cp = splitTopLevel(child);
2021
+ if (pp.length <= 1 && cp.length <= 1) {
2022
+ if (child.includes('&')) return child.split('&').join(parent);
2023
+ return parent + ' ' + child;
2024
+ }
2025
+ const out = [];
2026
+ for (const p of pp) for (const c of cp) out.push(c.includes('&') ? c.split('&').join(p) : p + ' ' + c);
2027
+ return out.join(', ');
2028
+ }
2029
+
2030
+ /** append a state to every part of a selector list: `.a, .b` + hover is `.a:hover, .b:hover` */
2031
+ function withState(sel, name) {
2032
+ const parts = splitTopLevel(sel);
2033
+ if (parts.length <= 1) return sel + ':' + name;
2034
+ return parts.map((p) => p + ':' + name).join(', ');
2035
  }
2036
 
2037
  /* v0.5.0 - where does a selector carry a pseudo-element?
public/llms.txt CHANGED
@@ -5,7 +5,7 @@
5
  - Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
6
  - Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
7
  - Install: `curl -fsSL https://dssl-sable.vercel.app/install.sh | sh` (or the zip: https://dssl-sable.vercel.app/dssl-engine.zip)
8
- - Version: 0.16.2 (engine), MIT license
9
  - Feature matrix vs CSS: see PARITY.md in the repo
10
  - For agents working ON the codebase: see AGENTS.md in the repo
11
 
@@ -64,6 +64,10 @@ DSSL.replay(sel) / DSSL.refresh([el]) / DSSL.errors() (a function) / DSSL.versio
64
  - Alpha on a named colour works: `white/12`, `currentColor/20`. It used to compile to `white/12px`.
65
  - The playground has a canvas.html tab, `?example=` links, lint warnings and an "as CSS" tab. The canvas keeps a short allow-list of tags and drops scripts, handlers, styles and ids.
66
 
 
 
 
 
67
  ## 0.16.2
68
  - `DSSL.lint`'s `css-loses` no longer judges the output of `dssl build` (its reset is built to sit under the engine), and ignores a rule whose value is the same as what DSSL wrote inline. On this site's docs page it reported 4,314 findings in 0.16.0; it now reports none from the site's own CSS.
69
 
@@ -132,4 +136,4 @@ Wait for the `dssl-ready` event on `window` before measuring layout: the engine
132
 
133
  ## Editor support
134
 
135
- The official extension (VS Code and VSCodium, same .vsix) bundles the real parser and resolver: diagnostics identical to the browser console, completions from the workspace token registry, hover docs for the semantic vocabulary. In VSCodium, search "DSSL" in the Extensions tab (it is on Open VSX: https://open-vsx.org/extension/ehs-studio/dssl-vscode). In VS Code, download `dssl-vscode/dssl-vscode-0.16.2.vsix` from the repo files, then Extensions panel -> ... -> Install from VSIX. Source lives in `dssl-vscode/` in the repo.
 
5
  - Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
6
  - Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
7
  - Install: `curl -fsSL https://dssl-sable.vercel.app/install.sh | sh` (or the zip: https://dssl-sable.vercel.app/dssl-engine.zip)
8
+ - Version: 0.16.3 (engine), MIT license
9
  - Feature matrix vs CSS: see PARITY.md in the repo
10
  - For agents working ON the codebase: see AGENTS.md in the repo
11
 
 
64
  - Alpha on a named colour works: `white/12`, `currentColor/20`. It used to compile to `white/12px`.
65
  - The playground has a canvas.html tab, `?example=` links, lint warnings and an "as CSS" tab. The canvas keeps a short allow-list of tags and drops scripts, handlers, styles and ids.
66
 
67
+ ## 0.16.3
68
+ - Seven bugs found by writing the native spec (`native/spec/`) are fixed. An unquoted `url(http://x.com/a.png)` no longer loses the rest of the line to the comment stripper (also inside `raw { }`). A typo at the end of a token path (`color.ink.bogus`) is an `unknown value` error instead of quietly becoming `color.ink`. Nesting under a selector list joins every part: `.a, .b { .c { } }` is `.a .c, .b .c`, `hover { }` gives `.a:hover, .b:hover`, `.c, .d { }` under `.a` gives `.a .c, .a .d`, and `::before { }` gets one rule per part. A backslash escapes a quote in a value (`content: "a\"b"`). A nested `li` or any other HTML element name no longer gets a "did you mean link" warning.
69
+ - Two new warnings, both compile fine: an unknown `@layer` name (it is treated as `components`) and `radius: xl` when no `radius.xl` token exists.
70
+
71
  ## 0.16.2
72
  - `DSSL.lint`'s `css-loses` no longer judges the output of `dssl build` (its reset is built to sit under the engine), and ignores a rule whose value is the same as what DSSL wrote inline. On this site's docs page it reported 4,314 findings in 0.16.0; it now reports none from the site's own CSS.
73
 
 
136
 
137
  ## Editor support
138
 
139
+ The official extension (VS Code and VSCodium, same .vsix) bundles the real parser and resolver: diagnostics identical to the browser console, completions from the workspace token registry, hover docs for the semantic vocabulary. In VSCodium, search "DSSL" in the Extensions tab (it is on Open VSX: https://open-vsx.org/extension/ehs-studio/dssl-vscode). In VS Code, download `dssl-vscode/dssl-vscode-0.16.3.vsix` from the repo files, then Extensions panel -> ... -> Install from VSIX. Source lives in `dssl-vscode/` in the repo.
public/showcase/wood/wood.css CHANGED
@@ -1,4 +1,4 @@
1
- /* generated by dssl build from tokens.dssl + static.dssl - the static subset of DSSL v0.16.2.
2
  @signal rules and engine-native properties (animate, aura, tilt, ...) stay in the runtime.
3
  Textures are baked in as background images when their colours are known at build time. */
4
  @import url("https://fonts.googleapis.com/css2?family=Fraunces:wght@500;700;800&family=Hanken+Grotesk:wght@400;500;700&display=swap");
 
1
+ /* generated by dssl build from tokens.dssl + static.dssl - the static subset of DSSL v0.16.3.
2
  @signal rules and engine-native properties (animate, aura, tilt, ...) stay in the runtime.
3
  Textures are baked in as background images when their colours are known at build time. */
4
  @import url("https://fonts.googleapis.com/css2?family=Fraunces:wght@500;700;800&family=Hanken+Grotesk:wght@400;500;700&display=swap");
scripts/gen-native-vocab.mjs ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+ /* gen-native-vocab.mjs: write the vocabulary the native crate and the spec appendices share.
3
+ *
4
+ * node scripts/gen-native-vocab.mjs write the files
5
+ * node scripts/gen-native-vocab.mjs --check fail if a file on disk is out of date
6
+ *
7
+ * Outputs:
8
+ * native/data/vocabulary.json every word list, in the engine's own order (order decides did-you-mean ties)
9
+ * native/spec/appendix-a-vocabulary.md the small tables, in prose form
10
+ * native/spec/appendix-b-messages.md every error and warning template found in the engine source
11
+ *
12
+ * The resolver keeps several tables private. Rather than touch the engine, this reads resolver.js as text,
13
+ * appends one export line to a temporary copy, and imports that. Nothing in public/dssl is changed. */
14
+ import { readFileSync, writeFileSync, mkdirSync, mkdtempSync, existsSync } from 'node:fs';
15
+ import { fileURLToPath, pathToFileURL } from 'node:url';
16
+ import { tmpdir } from 'node:os';
17
+ import { join, dirname } from 'node:path';
18
+
19
+ const root = fileURLToPath(new URL('../', import.meta.url));
20
+ const engineDir = join(root, 'public/dssl');
21
+ const check = process.argv.includes('--check');
22
+
23
+ async function loadResolverInternals() {
24
+ let src = readFileSync(join(engineDir, 'resolver.js'), 'utf8');
25
+ const base = pathToFileURL(engineDir).href + '/';
26
+ src = src.replace(/from '\.\//g, `from '${base}`);
27
+ src += '\nexport const __VOCAB = { TIME_PROPS, UNITLESS_FN, LENGTH_FN, COLOR_WORDS, FLUID_PROPS, PLACE_WORDS, SEMANTIC_NAMES: Object.keys(SEMANTIC) };\n';
28
+ const dir = mkdtempSync(join(tmpdir(), 'dssl-vocab-'));
29
+ const file = join(dir, 'resolver-vocab.mjs');
30
+ writeFileSync(file, src);
31
+ return import(pathToFileURL(file).href);
32
+ }
33
+
34
+ const R = await loadResolverInternals();
35
+ const P = await import(pathToFileURL(join(engineDir, 'parser.js')).href);
36
+ const C = await import(pathToFileURL(join(engineDir, 'color.js')).href);
37
+ const T = await import(pathToFileURL(join(engineDir, 'texture.js')).href);
38
+ const A = await import(pathToFileURL(join(engineDir, 'aura.js')).href);
39
+ const S = await import(pathToFileURL(join(engineDir, 'slider.js')).href);
40
+ const E = await import(pathToFileURL(join(engineDir, 'effects.js')).href);
41
+ const F = await import(pathToFileURL(join(engineDir, 'fonts.js')).href);
42
+
43
+ const V = R.__VOCAB;
44
+ const vocab = {
45
+ _about: 'Generated by scripts/gen-native-vocab.mjs from public/dssl. Do not edit by hand. Array order is the engine\'s insertion order.',
46
+ version: (/export const VERSION = '([^']+)'/.exec(readFileSync(join(engineDir, 'index.js'), 'utf8')) || [])[1] || 'unknown',
47
+ stateWords: [...P.STATE_WORDS],
48
+ pseudoElements: [...P.PSEUDO_ELEMENTS],
49
+ vendorPseudoSource: P.VENDOR_PSEUDO.source,
50
+ cssKeywords: [...R.CSS_KEYWORDS],
51
+ knownProps: [...R.KNOWN_PROPS],
52
+ unitlessProps: [...R.UNITLESS_PROPS],
53
+ timeProps: [...V.TIME_PROPS],
54
+ unitlessFns: [...V.UNITLESS_FN],
55
+ lengthFns: [...V.LENGTH_FN],
56
+ colorWords: [...V.COLOR_WORDS],
57
+ fluidProps: [...V.FLUID_PROPS],
58
+ placeWords: V.PLACE_WORDS,
59
+ easings: R.EASINGS,
60
+ defaultTokens: R.DEFAULT_TOKENS,
61
+ semanticNames: V.SEMANTIC_NAMES,
62
+ namedColors: C.NAMED,
63
+ texture: { generators: T.GENERATORS, blendModes: T.BLEND_MODES, keys: T.TEXTURE_KEYS, presetNames: T.PRESET_NAMES, presets: T.PRESETS },
64
+ aura: { fromWords: A.FROM_WORDS, shapes: A.SHAPES, dirWords: A.DIR_WORDS, blends: A.AURA_BLENDS, glowKeys: A.GLOW_KEYS, particleKeys: A.PARTICLE_KEYS, glowDefaults: A.GLOW_DEFAULTS, particleDefaults: A.PARTICLE_DEFAULTS, aroundNames: A.AROUND_NAMES },
65
+ slider: { clauses: S.SLIDER_CLAUSES, thumbShapes: S.THUMB_SHAPES },
66
+ effects: { glassWords: E.GLASS_WORDS, tiltWords: E.TILT_WORDS },
67
+ fonts: { words: F.FONT_WORDS },
68
+ };
69
+
70
+ /* ---------------- appendix A ---------------- */
71
+
72
+ const code = (x) => '`' + x + '`';
73
+ const list = (a) => a.map(code).join(', ');
74
+ const table = (head, rows) => [`| ${head.join(' | ')} |`, `|${head.map(() => '---').join('|')}|`, ...rows.map((r) => `| ${r.join(' | ')} |`)].join('\n');
75
+ const esc = (s) => String(s).replace(/\|/g, '\\|');
76
+
77
+ function appendixA() {
78
+ const o = [];
79
+ o.push('# Appendix A: vocabulary', '',
80
+ 'Generated by `scripts/gen-native-vocab.mjs` from the engine. Not edited by hand.',
81
+ `The full word lists, in the engine's own order, are in \`native/data/vocabulary.json\`.`, '');
82
+ o.push('## A.1 State words', '', list(vocab.stateWords), '');
83
+ o.push('## A.2 Pseudo-elements', '', list(vocab.pseudoElements), '',
84
+ `A vendor pseudo-element name matches \`${vocab.vendorPseudoSource}\`.`, '');
85
+ o.push('## A.3 Unitless and time properties', '',
86
+ 'Unitless (a bare number stays bare):', '', list(vocab.unitlessProps), '',
87
+ 'Time (a bare number is milliseconds):', '', list(vocab.timeProps), '');
88
+ o.push('## A.4 Functions', '',
89
+ 'Unitless functions (numbers inside stay bare):', '', list(vocab.unitlessFns), '',
90
+ 'Length functions (bare numbers inside are px):', '', list(vocab.lengthFns), '');
91
+ o.push('## A.5 Fluid properties', '', list(vocab.fluidProps), '');
92
+ o.push('## A.6 Place words', '', table(['word', 'cross', 'main'], Object.entries(vocab.placeWords).map(([k, v]) => [code(k), code(v[0]), code(v[1])])), '');
93
+ o.push('## A.7 Easings', '', table(['name', 'CSS'], Object.entries(vocab.easings).map(([k, v]) => [code(k), code(v)])), '');
94
+ o.push('## A.8 Built-in tokens', '', table(['key', 'value'], Object.entries(vocab.defaultTokens).map(([k, v]) => [code(k), code(esc(v))])), '');
95
+ o.push('## A.9 Semantic properties', '', list(vocab.semanticNames), '');
96
+ o.push('## A.10 Big sets', '', table(['set', 'count', 'where'], [
97
+ ['CSS keywords', vocab.cssKeywords.length, '`cssKeywords` in the data file'],
98
+ ['known CSS properties', vocab.knownProps.length, '`knownProps`'],
99
+ ['named colors', Object.keys(vocab.namedColors).length, '`namedColors`'],
100
+ ]), '', 'Color words allowed as gradient stops:', '', list(vocab.colorWords), '');
101
+ o.push('## A.11 Texture', '',
102
+ `Generators: ${list(vocab.texture.generators)}`, '',
103
+ `Blend modes: ${list(vocab.texture.blendModes)}`, '',
104
+ `Presets (${vocab.texture.presetNames.length}): ${list(vocab.texture.presetNames)}`, '',
105
+ 'Settings:', '',
106
+ table(['key', 'kind', 'values', 'range', 'what it does'], Object.entries(vocab.texture.keys).map(([k, d]) => [
107
+ code(k), d.kind, d.words ?? '', d.values ? list(d.values) : (d.min !== undefined ? `${d.min} to ${d.max}` : ''), esc(d.doc || ''),
108
+ ])), '');
109
+ o.push('## A.12 Aura', '',
110
+ `From words: ${list(vocab.aura.fromWords)}`, '',
111
+ `Shapes: ${list(vocab.aura.shapes)}`, '',
112
+ `Direction words: ${list(Object.keys(vocab.aura.dirWords))}`, '',
113
+ `Blends: ${list(vocab.aura.blends)}`, '',
114
+ `Around shapes: ${list(vocab.aura.aroundNames)}`, '',
115
+ 'Glow settings:', '',
116
+ table(['key', 'kind', 'range', 'what it does'], Object.entries(vocab.aura.glowKeys).map(([k, d]) => [code(k), d.kind, d.min !== undefined ? `${d.min} to ${d.max}` : (d.values ? list(d.values) : ''), esc(d.doc || '')])), '',
117
+ `Glow defaults: ${code(JSON.stringify(vocab.aura.glowDefaults))}`, '',
118
+ 'Particle settings:', '',
119
+ table(['key', 'kind', 'range', 'what it does'], Object.entries(vocab.aura.particleKeys).map(([k, d]) => [code(k), d.kind, d.min !== undefined ? `${d.min} to ${d.max}` : (d.values ? list(d.values) : ''), esc(d.doc || '')])), '',
120
+ `Particle defaults: ${code(JSON.stringify(vocab.aura.particleDefaults))}`, '');
121
+ o.push('## A.13 Slider', '', table(['clause', 'form'], Object.entries(vocab.slider.clauses).map(([k, v]) => [code(k), code(esc(v))])), '',
122
+ `Thumb shapes: ${list(vocab.slider.thumbShapes)}`, '');
123
+ o.push('## A.14 Glass and tilt', '',
124
+ table(['word', 'meaning'], [...Object.entries(vocab.effects.glassWords), ...Object.entries(vocab.effects.tiltWords)].map(([k, v]) => [code(k), esc(v)])), '');
125
+ o.push('## A.15 Fonts', '', table(['word', 'meaning'], Object.entries(vocab.fonts.words).map(([k, v]) => [code(k), esc(v)])), '');
126
+ return o.join('\n') + '\n';
127
+ }
128
+
129
+ /* ---------------- appendix B ---------------- */
130
+
131
+ function appendixB() {
132
+ const files = ['parser.js', 'resolver.js', 'texture.js', 'aura.js', 'slider.js', 'effects.js', 'fonts.js', 'lint.js'];
133
+ const re = /(?:\bmessage:\s*|\berr\(\s*\w+\s*,\s*|\b(?:bad|hooks\.error)\(\s*)(`(?:[^`\\]|\\.)*`|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")/g;
134
+ const o = ['# Appendix B: message templates', '',
135
+ 'Generated by `scripts/gen-native-vocab.mjs`. Every error and warning template found in the engine',
136
+ 'source, by file and line. `${...}` marks a value filled in at run time. The extraction is by pattern,',
137
+ 'so it can include a string that is not a message and can miss one built in an unusual way. It is a',
138
+ 'checklist for the corpus, not the definition: the definition is in the chapters.', ''];
139
+ for (const f of files) {
140
+ const text = readFileSync(join(engineDir, f), 'utf8');
141
+ const seen = new Set();
142
+ const rows = [];
143
+ for (const m of text.matchAll(re)) {
144
+ const raw = m[1].slice(1, -1).replace(/\\n/g, ' ').replace(/\\(['"`])/g, '$1');
145
+ if (raw.length < 6 || seen.has(raw)) continue;
146
+ seen.add(raw);
147
+ const line = text.slice(0, m.index).split('\n').length;
148
+ rows.push([String(line), code(esc(raw).replace(/`/g, "'"))]);
149
+ }
150
+ o.push(`## ${f}`, '', table(['line', 'template'], rows), '');
151
+ }
152
+ return o.join('\n') + '\n';
153
+ }
154
+
155
+ /* ---------------- write or check ---------------- */
156
+
157
+ const outputs = [
158
+ ['native/data/vocabulary.json', JSON.stringify(vocab, null, 1) + '\n'],
159
+ ['native/spec/appendix-a-vocabulary.md', appendixA()],
160
+ ['native/spec/appendix-b-messages.md', appendixB()],
161
+ ];
162
+
163
+ let stale = 0;
164
+ for (const [rel, text] of outputs) {
165
+ const path = join(root, rel);
166
+ if (check) {
167
+ const now = existsSync(path) ? readFileSync(path, 'utf8') : null;
168
+ if (now !== text) { console.error('STALE ' + rel + ' (run node scripts/gen-native-vocab.mjs)'); stale++; }
169
+ continue;
170
+ }
171
+ mkdirSync(dirname(path), { recursive: true });
172
+ writeFileSync(path, text);
173
+ console.log('wrote ' + rel + ' (' + text.length + ' bytes)');
174
+ }
175
+ if (check) {
176
+ if (stale) process.exit(1);
177
+ console.log('vocabulary files are up to date');
178
+ }
scripts/test-all.mjs CHANGED
@@ -15,7 +15,7 @@ const SUITES = [
15
  ['engine 0.5', 'test-v050.mjs'], ['engine 0.6', 'test-v060.mjs'], ['engine 0.7', 'test-v070.mjs'], ['parser lines 0.7.1', 'test-v071.mjs'],
16
  ['review fixes 0.7.2', 'test-v072.mjs'], ['exit lane 0.8.0', 'test-v073.mjs'], ['follow-up 0.8.1', 'test-v081.mjs'], ['cascade 0.8.2', 'test-v082.mjs'],
17
  ['color, texture, aura, slider, lint, import, share 0.9.0', 'test-v090.mjs'], ['editor extension', 'test-extension-lib.mjs'],
18
- ['site stylesheets compile', 'dssl-styles-check.mjs'], ['guide examples compile', 'test-guide-examples.mjs'],
19
  ['designer: state to DSSL and back', 'test-designer.mjs'], ['designer: editor wiring (fake vscode)', 'test-designer-host.mjs'],
20
  ['guide examples in a browser', 'test-guide-browser.mjs', true], ['0.9.0 in a browser', 'test-browser-v090.mjs', true], ['designer panel in a browser', 'test-designer-browser.mjs', true], ['glass, tilt, wood, baked textures, counters 0.10-0.11', 'test-v0100.mjs', true], ['symmetrical, fonts, gradients, inspector, hot reload 0.12', 'test-v0120.mjs', true], ['build fidelity, source maps, strict CSP, server rendering 0.13', 'test-v0130.mjs', true], ['made for games, docs match the code 0.14', 'test-v0140.mjs', true], ['the second game review 0.15', 'test-v0150.mjs', true], ['round three: yield, shapes, textures 0.16', 'test-v0160.mjs', true], ['built CSS with JavaScript off vs the engine, every page', 'test-static-parity.mjs', true],
21
  ];
 
15
  ['engine 0.5', 'test-v050.mjs'], ['engine 0.6', 'test-v060.mjs'], ['engine 0.7', 'test-v070.mjs'], ['parser lines 0.7.1', 'test-v071.mjs'],
16
  ['review fixes 0.7.2', 'test-v072.mjs'], ['exit lane 0.8.0', 'test-v073.mjs'], ['follow-up 0.8.1', 'test-v081.mjs'], ['cascade 0.8.2', 'test-v082.mjs'],
17
  ['color, texture, aura, slider, lint, import, share 0.9.0', 'test-v090.mjs'], ['editor extension', 'test-extension-lib.mjs'],
18
+ ['site stylesheets compile', 'dssl-styles-check.mjs'], ['guide examples compile', 'test-guide-examples.mjs'], ['native spec: examples, prose, known bugs', 'test-native-spec.mjs'], ['known divergences fixed 0.16.3', 'test-v0163.mjs'],
19
  ['designer: state to DSSL and back', 'test-designer.mjs'], ['designer: editor wiring (fake vscode)', 'test-designer-host.mjs'],
20
  ['guide examples in a browser', 'test-guide-browser.mjs', true], ['0.9.0 in a browser', 'test-browser-v090.mjs', true], ['designer panel in a browser', 'test-designer-browser.mjs', true], ['glass, tilt, wood, baked textures, counters 0.10-0.11', 'test-v0100.mjs', true], ['symmetrical, fonts, gradients, inspector, hot reload 0.12', 'test-v0120.mjs', true], ['build fidelity, source maps, strict CSP, server rendering 0.13', 'test-v0130.mjs', true], ['made for games, docs match the code 0.14', 'test-v0140.mjs', true], ['the second game review 0.15', 'test-v0150.mjs', true], ['round three: yield, shapes, textures 0.16', 'test-v0160.mjs', true], ['built CSS with JavaScript off vs the engine, every page', 'test-static-parity.mjs', true],
21
  ];
scripts/test-native-spec.mjs ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+ /* test-native-spec.mjs: keep native/spec honest.
3
+ *
4
+ * 1. every ```dssl block in the spec compiles with no errors, and every ```dssl error block has at least one
5
+ * 2. the numbers and strings the prose promises are what the engine produces
6
+ * 3. the divergences of chapter 12 stay fixed
7
+ * 4. the generated appendices and data file are up to date
8
+ *
9
+ * Exit code is 0 only when every check passed. */
10
+ import { readFileSync, readdirSync } from 'node:fs';
11
+ import { spawnSync } from 'node:child_process';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { join } from 'node:path';
14
+ import { parse } from '../public/dssl/parser.js';
15
+ import { resolve, DEFAULT_TOKENS } from '../public/dssl/resolver.js';
16
+
17
+ const root = fileURLToPath(new URL('../', import.meta.url));
18
+ const specDir = join(root, 'native/spec');
19
+
20
+ let pass = 0, fail = 0;
21
+ const ok = (name) => { pass++; };
22
+ const bad = (name, why) => { fail++; console.error('FAIL ' + name + (why ? '\n ' + why : '')); };
23
+
24
+ function run(src, file = 'spec.dssl') {
25
+ const p = parse(src, file);
26
+ const d = resolve(p.ast, file, { ...DEFAULT_TOKENS });
27
+ return { doc: d, errors: [...p.errors, ...d.errors], warnings: d.warnings || [] };
28
+ }
29
+ const rule = (doc, sel) => doc.rules.find((r) => r.sel === sel);
30
+ const val = (doc, sel, prop) => { const r = rule(doc, sel); const d = r && r.decls.find((x) => x.prop === prop); return d ? d.value : undefined; };
31
+
32
+ /* 1. fenced examples */
33
+ const chapters = readdirSync(specDir).filter((f) => /^\d\d-.*\.md$/.test(f)).sort();
34
+ let blocks = 0;
35
+ for (const f of chapters) {
36
+ const text = readFileSync(join(specDir, f), 'utf8');
37
+ let i = 0;
38
+ for (const m of text.matchAll(/```dssl( error)?\n([\s\S]*?)```/g)) {
39
+ i++; blocks++;
40
+ const expectError = !!m[1];
41
+ const { errors } = run(m[2], `${f} block ${i}`);
42
+ if (expectError && errors.length) ok();
43
+ else if (expectError) bad(`${f} block ${i}`, 'is marked "error" but compiled cleanly');
44
+ else if (errors.length) bad(`${f} block ${i}`, errors.map((e) => `line ${e.line}: ${e.message}`).join('; '));
45
+ else ok();
46
+ }
47
+ }
48
+ if (blocks < 20) bad('spec examples', `only ${blocks} fenced blocks found, expected the chapters to carry many more`);
49
+
50
+ /* 2. promises made in the prose */
51
+ const eq = (name, got, want) => (JSON.stringify(got) === JSON.stringify(want) ? ok() : bad(name, `got ${JSON.stringify(got)}, wanted ${JSON.stringify(want)}`));
52
+
53
+ { // 3.2 numbers
54
+ const { doc } = run('.a { padding: 0; margin: 0 auto; opacity: 0.5; line-height: 1.5; transition-delay: 200 }');
55
+ eq('VAL-3 padding', val(doc, '.a', 'padding'), '0px');
56
+ eq('VAL-3 margin', val(doc, '.a', 'margin'), '0px auto');
57
+ eq('VAL-3 opacity', val(doc, '.a', 'opacity'), '0.5');
58
+ eq('VAL-3 line-height', val(doc, '.a', 'line-height'), '1.5');
59
+ eq('3.2 time prop', val(doc, '.a', 'transition-delay'), '200ms');
60
+ }
61
+ { // 3.3 functions
62
+ const { doc } = run('.a { width: calc(100% - 24); background: oklch(62% 0.19 35); grid-template-columns: repeat(4, 1fr); transform: scale(1.2) }');
63
+ eq('VAL-7 calc', val(doc, '.a', 'width'), 'calc(100% - 24px)');
64
+ eq('VAL-7 oklch', val(doc, '.a', 'background'), 'oklch(62% 0.19 35)');
65
+ eq('VAL-6 repeat', val(doc, '.a', 'grid-template-columns'), 'repeat(4, 1fr)');
66
+ eq('VAL-7 scale', val(doc, '.a', 'transform'), 'scale(1.2)');
67
+ }
68
+ { // 3.4 alpha
69
+ const { doc } = run('tokens { color { x: #123456 } }\n.a { color: color.x/50; background: #ff0000/50; border-color: white/12 }');
70
+ eq('VAL-8 token alpha', val(doc, '.a', 'color'), 'color-mix(in oklab, var(--dssl-color-x) 50%, transparent)');
71
+ eq('VAL-8 hex alpha', val(doc, '.a', 'background'), 'color-mix(in oklab, #ff0000 50%, transparent)');
72
+ eq('VAL-8 named alpha', val(doc, '.a', 'border-color'), 'color-mix(in oklab, white 12%, transparent)');
73
+ }
74
+ { // 3.7 fluid
75
+ const { doc } = run('h1 { font-size: fluid 16 24 }');
76
+ eq('VAL-13 fluid', val(doc, 'h1', 'font-size'), 'clamp(16px, calc(12.571px + 0.952vw), 24px)');
77
+ }
78
+ { // 5.2 layers
79
+ const { doc } = run('.a { color: red }\npin { .a { color: blue } }\n@layer base { .a { color: green } }');
80
+ eq('CAS-2/5 layer ranks', doc.rules.filter((r) => r.sel === '.a').map((r) => [r.layer, val(doc.rules.length ? { rules: [r] } : doc, '.a', 'color')]), [['components', 'red'], ['pin', 'blue'], ['base', 'green']]);
81
+ }
82
+ { // 5.4 fragments
83
+ const { doc } = run('.a {\n color: red\n hover { color: blue }\n padding: 4\n}');
84
+ eq('CAS-12 fragments', doc.rules.filter((r) => r.sel.startsWith('.a')).map((r) => [r.sel, r.decls[0].prop]), [['.a', 'color'], ['.a:hover', 'color'], ['.a', 'padding']]);
85
+ const orders = doc.rules.filter((r) => r.sel.startsWith('.a')).map((r) => r.order);
86
+ eq('CAS-11 orders 1,2,3', orders, [1, 2, 3]);
87
+ eq('TOK-9 registry order 0', doc.registryRule.order, 0);
88
+ }
89
+ { // 5.5 media
90
+ const { doc } = run('.a { @media 768 { padding: 24 } @media <= 480 { padding: 12 } @media 480..960 { gap: 4 } @media dark { gap: 1 } }');
91
+ eq('CAS-19 media', doc.rules.filter((r) => r.sel === '.a').map((r) => r.media), [['(min-width: 768px)'], ['(max-width: 480px)'], ['(min-width: 480px) and (max-width: 960px)'], ['(prefers-color-scheme: dark)']]);
92
+ }
93
+ { // 5.9 signals
94
+ const { doc } = run('.a { @signal theme == dark { color: red } @signal screen in (a, b) { color: blue } @signal on { color: green } }');
95
+ eq('CAS-37 signals', doc.rules.filter((r) => r.sel === '.a').map((r) => r.signals[0]), [
96
+ { name: 'theme', op: '==', value: 'dark' }, { name: 'screen', op: 'in', value: 'a,b', values: ['a', 'b'] }, { name: 'on', op: 'truthy', value: null }]);
97
+ }
98
+ { // 6.3 row
99
+ const { doc } = run('.toolbar { row: gap 8 between center }');
100
+ eq('PRP-8 row', rule(doc, '.toolbar').decls.map((d) => d.prop + ':' + d.value), ['display:flex', 'flex-direction:row', 'gap:8px', 'justify-content:space-between', 'align-items:center']);
101
+ }
102
+ { // 4.3 conditional tokens
103
+ const { doc } = run('tokens { color { bg: white } }\n@media dark { tokens { color { bg: black } } }\n.a { background: color.bg }');
104
+ const toks = doc.rules.filter((r) => r.layer === 'tokens');
105
+ eq('TOK-8 two token rules', toks.map((r) => [r.media, r.decls[0].value]), [[[], 'white'], [['(prefers-color-scheme: dark)'], 'black']]);
106
+ eq('TOK-9 registry carries the last definition', doc.registryRule.decls.find((d) => d.prop === '--dssl-color-bg').value, 'black');
107
+ }
108
+ { // 3.8, 6.1
109
+ const { errors } = run('.a { color: red !important }');
110
+ eq('VAL-15 important', errors.length === 1 && /^!important does not exist in DSSL/.test(errors[0].message), true);
111
+ }
112
+
113
+ /* 3. the divergences of chapter 12 stay fixed (0.16.3). D-6 is open and needs a browser test, so it is not checked here. */
114
+ { // D-1, SYN-6
115
+ const { errors, doc } = run('.a { background: url(http://x.com/a.png) }');
116
+ eq('D-1 unquoted url keeps its //', [errors.length, val(doc, '.a', 'background')], [0, 'url(http://x.com/a.png)']);
117
+ }
118
+ { // D-2, TOK-12
119
+ const { errors } = run('tokens { color { ink2: #111 } }\n.a { color: color.ink2.bogus }');
120
+ eq('D-2 a typo in a token path is an error', errors.some((e) => /unknown value "color.ink2.bogus"/.test(e.message)), true);
121
+ }
122
+ { // D-3, CAS-2
123
+ const { errors, warnings } = run('@layer weird { .a { color: red } }');
124
+ eq('D-3 unknown layer warns', [errors.length, warnings.some((w) => /^unknown layer "weird", treated as "components"/.test(w.message))], [0, true]);
125
+ }
126
+ { // D-4, TOK-18
127
+ const { doc, errors, warnings } = run('.a { radius: xl }');
128
+ eq('D-4 radius xl warns, same output', [errors.length, warnings.some((w) => /^radius "xl" is not defined/.test(w.message)), val(doc, '.a', 'border-radius')], [0, true, 'var(--dssl-radius-xl)']);
129
+ }
130
+ { // D-5, VAL string rule
131
+ const { errors } = run('.a { content: "a\\"b" }');
132
+ eq('D-5 an escaped quote stays inside the string', errors.length, 0);
133
+ }
134
+ { // D-7, CAS-7, CAS-24
135
+ const sels = (src, hit) => run(src).doc.rules.filter((r) => hit.test(r.sel)).map((r) => r.sel);
136
+ eq('D-7 parent list', sels('.a, .b { .c { color: red } }', /\.c/), ['.a .c, .b .c']);
137
+ eq('D-7 state on a list', sels('.a, .b { hover { color: red } }', /hover/), ['.a:hover, .b:hover']);
138
+ eq('D-7 child list', sels('.a { .c, .d { color: red } }', /\.c/), ['.a .c, .a .d']);
139
+ }
140
+ { // D-8, CAS-8
141
+ const { warnings } = run('.menu { li { color: red } hove { color: red } }');
142
+ eq('D-8 li is quiet, hove still warns', [warnings.some((w) => /"li" compiled/.test(w.message)), warnings.some((w) => /"hove" compiled/.test(w.message))], [false, true]);
143
+ }
144
+
145
+ /* 4. generated files */
146
+ {
147
+ const r = spawnSync(process.execPath, ['--no-warnings', join(root, 'scripts/gen-native-vocab.mjs'), '--check'], { encoding: 'utf8' });
148
+ if (r.status === 0) ok(); else bad('generated vocabulary files are up to date', (r.stderr || r.stdout || '').trim());
149
+ }
150
+
151
+ console.log(`${blocks} spec examples checked`);
152
+ console.log(`${pass} passed, ${fail} failed`);
153
+ process.exit(fail ? 1 : 0);
scripts/test-v0163.mjs ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+ /* test-v0163.mjs: seven known divergences fixed (D-1 to D-5, D-7, D-8): unquoted url() with //, token prefix matching,
3
+ * unknown @layer, undefined radius word, escaped quotes in values, selector lists when nesting, HTML element names
4
+ * that look like state typos. Pure Node, no browser. */
5
+ import { compile } from '../public/dssl/index.js';
6
+
7
+ let pass = 0, fail = 0;
8
+ const ok = (name, cond, extra) => { if (cond) { pass++; console.log(` ok ${name}`); } else { fail++; console.log(`FAIL ${name}${extra !== undefined ? '; ' + extra : ''}`); } };
9
+ const section = (t) => console.log(`\n== ${t}`);
10
+ const run = (src) => compile(src, 't.dssl');
11
+ const sels = (src) => run(src).doc.rules.filter((r) => r.layer !== 'tokens').map((r) => r.sel);
12
+ const shadow = (src) => run(src).doc.shadowRules.map((r) => `${r.sel}::${r.pseudo}`);
13
+ const decl = (src, prop) => { for (const r of run(src).doc.rules) for (const d of r.decls) if (d.prop === prop) return d.value; return undefined; };
14
+ const J = JSON.stringify;
15
+
16
+ section('D-1. // inside an unquoted url()');
17
+ {
18
+ const r = run('.a { background: url(http://x.com/a.png) }');
19
+ ok('unquoted url with // compiles clean', r.errors.length === 0 && r.ok, J(r.errors));
20
+ ok('and keeps the whole url', decl('.a { background: url(http://x.com/a.png) }', 'background') === 'url(http://x.com/a.png)');
21
+ ok('a declaration after it on the next line survives', decl('.a { background: url(http://x.com/a.png)\n color: red }', 'color') === 'red');
22
+ ok('upper case URL( with spaces', run('.a { background: URL( http://x.com/a.png ) }').errors.length === 0);
23
+ ok('inside raw { } the closing brace is kept', run('raw { .a { background: url(http://x.com/a.png) } }\n.b { color: red }').errors.length === 0);
24
+ ok('quoted url still fine', run('.a { background: url("http://x.com/a.png") }').errors.length === 0 && decl('.a { background: url("http://x.com/a.png") }', 'background') === 'url("http://x.com/a.png")');
25
+ ok('a real comment after the url still works', decl('.a { background: url(a.png) // note\n color: red }', 'color') === 'red' && run('.a { background: url(a.png) // note\n color: red }').errors.length === 0);
26
+ ok('a // comment elsewhere still strips', run('.a { color: red } // trailing\n.b { color: blue }').errors.length === 0);
27
+ const e = run('.a { background: url(http://x.com/a.png)\n color: nonsense }').errors;
28
+ ok('line numbers do not drift after the url', e.length === 1 && e[0].line === 2, J(e));
29
+ }
30
+
31
+ section('D-2. token lookup is exact');
32
+ {
33
+ const src = 'tokens { color { ink2: #111 } }\n.a { color: color.ink2.bogus }';
34
+ const r = run(src);
35
+ ok('color.ink2.bogus is an unknown value', r.errors.length === 1 && /unknown value "color\.ink2\.bogus"/.test(r.errors[0].message), J(r.errors));
36
+ ok('the error carries file, line, message, hint keys', r.errors[0] && ['file', 'line', 'message', 'hint'].every((k) => k in r.errors[0]));
37
+ ok('color.ink2 itself still resolves', decl('tokens { color { ink2: #111 } }\n.a { color: color.ink2 }', 'color') === 'var(--dssl-color-ink2)');
38
+ ok('alpha on an exact token still works', /color-mix/.test(decl('tokens { color { ink2: #111 } }\n.a { color: color.ink2/50 }', 'color')));
39
+ ok('built-in tokens still resolve', run('.a { color: color.ink }').errors.length === 0);
40
+ }
41
+
42
+ section('D-3. unknown @layer warns');
43
+ {
44
+ const r = run('@layer weird { .a { color: red } }');
45
+ ok('warns, never errors', r.errors.length === 0 && r.ok && r.warnings.length === 1, J(r.warnings));
46
+ ok('message and hint', r.warnings[0].message === 'unknown layer "weird", treated as "components"' && r.warnings[0].hint === 'the layers are tokens, base, components, utilities, pin', J(r.warnings));
47
+ ok('warning has the usual shape', ['file', 'line', 'message', 'hint'].every((k) => k in r.warnings[0]) && r.warnings[0].line === 1);
48
+ ok('still compiled as components', r.doc.rules.find((x) => x.sel === '.a').layer === 'components');
49
+ const t = run('@layer compnents { .a { color: red } }');
50
+ ok('a near miss gets a did-you-mean', t.warnings.length === 1 && t.warnings[0].hint === 'components', J(t.warnings));
51
+ ok('known layers are silent', ['tokens', 'base', 'components', 'utilities', 'pin'].every((l) => run(`@layer ${l} { .a { color: red } }`).warnings.length === 0));
52
+ ok('pin { } is silent', run('pin { .a { color: red } }').warnings.length === 0);
53
+ }
54
+
55
+ section('D-4. radius word with no token');
56
+ {
57
+ const r = run('.a { radius: xl }');
58
+ ok('radius: xl warns', r.errors.length === 0 && r.ok && r.warnings.some((w) => w.message === 'radius "xl" is not defined, so it will not apply' && w.hint === 'add it with tokens { radius { xl: 32 } }'), J(r.warnings));
59
+ ok('same declaration is still emitted', decl('.a { radius: xl }', 'border-radius') === 'var(--dssl-radius-xl)');
60
+ ok('no default token was added', !('radius.xl' in run('.a { radius: xl }').doc.tokens));
61
+ ok('defining it silences the warning', run('tokens { radius { xl: 32 } }\n.a { radius: xl }').warnings.length === 0);
62
+ ok('sm, md, lg, full are silent', ['sm', 'md', 'lg', 'full'].every((w) => run(`.a { radius: ${w} }`).warnings.length === 0));
63
+ ok('a number is silent', run('.a { radius: 8 }').warnings.length === 0);
64
+ ok('a custom radius token is silent', run('tokens { radius { pill: 99 } }\n.a { radius: pill }').warnings.length === 0);
65
+ }
66
+
67
+ section('D-5. a backslash escapes a quote in a value');
68
+ {
69
+ const r = run('.a { content: "a\\"b" }');
70
+ ok('content: "a\\"b" has no error', r.errors.length === 0, J(r.errors));
71
+ ok('and is passed through', decl('.a { content: "a\\"b" }', 'content') === '"a\\"b"');
72
+ ok('single quotes too', run(".a { content: 'it\\'s' }").errors.length === 0);
73
+ ok('inside a function', run('.a { content: attr(x) "q\\"r" }').errors.length === 0 && run('.a { background: image-set("a\\"b.png" 1x) }').errors.length === 0);
74
+ ok('a plain string is unchanged', decl('.a { content: "ab" }', 'content') === '"ab"');
75
+ ok('an unknown word after an escaped string is still an error', run('.a { content: "a\\"b" nonsense }').errors.length === 1);
76
+ }
77
+
78
+ section('D-7. nesting under selector lists');
79
+ {
80
+ ok('.a, .b { .c { } }', J(sels('.a, .b { .c { color: red } }')) === J(['.a .c, .b .c']), J(sels('.a, .b { .c { color: red } }')));
81
+ ok('.a, .b { hover { } }', J(sels('.a, .b { hover { color: red } }')) === J(['.a:hover, .b:hover']), J(sels('.a, .b { hover { color: red } }')));
82
+ ok('.a { .c, .d { } }', J(sels('.a { .c, .d { color: red } }')) === J(['.a .c, .a .d']), J(sels('.a { .c, .d { color: red } }')));
83
+ ok('.a, .b { .c, .d { } } is parent-major', J(sels('.a, .b { .c, .d { color: red } }')) === J(['.a .c, .a .d, .b .c, .b .d']), J(sels('.a, .b { .c, .d { color: red } }')));
84
+ ok('.a, .b { &.on { } }', J(sels('.a, .b { &.on { color: red } }')) === J(['.a.on, .b.on']), J(sels('.a, .b { &.on { color: red } }')));
85
+ ok('.a, .b { & > .c { } }', J(sels('.a, .b { & > .c { color: red } }')) === J(['.a > .c, .b > .c']));
86
+ ok('.a { .x &, & + & { } } substitutes every &', J(sels('.a { .x &, & + & { color: red } }')) === J(['.x .a, .a + .a']), J(sels('.a { .x &, & + & { color: red } }')));
87
+ ok('state inside a nested list child', J(sels('.a, .b { .c { hover { color: red } } }')) === J(['.a .c:hover, .b .c:hover']));
88
+ ok('a comma inside :is( ) is NOT split (parent)', J(sels('.a:is(.x, .y) { .c { color: red } }')) === J(['.a:is(.x, .y) .c']));
89
+ ok('a comma inside :is( ) is NOT split (child)', J(sels('.a { .c:is(.x, .y) { color: red } }')) === J(['.a .c:is(.x, .y)']));
90
+ ok('a comma inside [ ] is NOT split', J(sels('.a { [data-x="1,2"] { color: red } }')) === J(['.a [data-x="1,2"]']));
91
+ ok('a state on :is( ) parent', J(sels('.a:is(.x, .y) { hover { color: red } }')) === J(['.a:is(.x, .y):hover']));
92
+ ok('plain .a { .b { } } is unchanged', J(sels('.a { .b { color: red } }')) === J(['.a .b']));
93
+ ok('plain & is unchanged', J(sels('.a { &.on { color: red } }')) === J(['.a.on']));
94
+ ok('plain state is unchanged', J(sels('.a { hover { color: red } }')) === J(['.a:hover']));
95
+ ok('deep plain nesting is unchanged', J(sels('.a { .b { .c { color: red } } }')) === J(['.a .b .c']));
96
+ ok('no errors for any of it', run('.a, .b { .c, .d { hover { color: red } } }').errors.length === 0);
97
+ const sh = shadow('.a, .b { ::before { content: ""\n color: red } }');
98
+ ok('.a, .b { ::before { } } gives one shadow rule per part', J(sh) === J(['.a::before', '.b::before']), J(sh));
99
+ const sh2 = shadow('.a, .b { ::before { content: ""\n hover { color: red } } }');
100
+ ok('and a state inside it follows each part', J(sh2) === J(['.a::before', '.a:hover::before', '.b::before', '.b:hover::before']), J(sh2));
101
+ ok('plain .a { ::before { } } is unchanged', J(shadow('.a { ::before { content: "" } }')) === J(['.a::before']));
102
+ ok('the shadow rules do not share declaration objects', (() => { const r = run('.a, .b { ::before { content: "" } }').doc.shadowRules; return r.length === 2 && r[0].decls !== r[1].decls && r[0].id !== r[1].id; })());
103
+ ok('a top-level list of pseudo-elements is unchanged', J(shadow('.a::before, .b::before { content: "" }')) === J(['.a::before', '.b::before']));
104
+ }
105
+
106
+ section('D-8. HTML element names are not state typos');
107
+ {
108
+ const w = (src) => run(src).warnings.filter((x) => /compiled as a descendant selector/.test(x.message));
109
+ ok('li under a rule does not warn', w('.menu { li { color: red } }').length === 0);
110
+ const names = 'a abbr address article aside audio b blockquote body br button canvas caption code col details dialog div em fieldset figure footer form h1 h2 h3 h4 h5 h6 header hr html i iframe img input label legend li main mark menu nav ol optgroup option p pre section select small span strong sub summary sup table tbody td textarea tfoot th thead time tr u ul video'.split(' ');
111
+ const noisy = names.filter((n) => w(`.x { ${n} { color: red } }`).length);
112
+ ok(`none of ${names.length} element names warn`, noisy.length === 0, noisy.join(','));
113
+ ok('and they still compile as descendants', J(sels('.menu { li { color: red } }')) === J(['.menu li']));
114
+ ok('hove still warns', w('.a { hove { color: red } }').length === 1 && /"hover"/.test(w('.a { hove { color: red } }')[0].hint));
115
+ ok('focs still warns', w('.a { focs { color: red } }').length === 1 && /"focus"/.test(w('.a { focs { color: red } }')[0].hint));
116
+ ok('warnings never become errors', run('.a { hove { color: red } }').errors.length === 0 && run('.a { hove { color: red } }').ok);
117
+ ok('a custom word far from any state is silent', w('.a { widget { color: red } }').length === 0);
118
+ }
119
+
120
+ console.log(`\n${pass} passed, ${fail} failed`);
121
+ process.exit(fail ? 1 : 0);
src/components/dssl/views/ChangelogView.tsx CHANGED
@@ -11,6 +11,14 @@ const ENTRIES: {
11
  code?: string;
12
  kind?: "spec" | "vote" | "add";
13
  }[] = [
 
 
 
 
 
 
 
 
14
  {
15
  version: "v0.16.2",
16
  date: "growth phase \u00b7 a second small fix",
 
11
  code?: string;
12
  kind?: "spec" | "vote" | "add";
13
  }[] = [
14
+ {
15
+ version: "v0.16.3",
16
+ date: "growth phase \u00b7 the spec found seven bugs",
17
+ title: "Writing the native spec found seven bugs. They are fixed.",
18
+ why: "a language a browser could implement needs a spec that can be checked against the engine, and checking it showed where the engine and the language disagreed.",
19
+ kind: "add",
20
+ body: "An unquoted url(http://...) no longer loses the rest of the line. A typo at the end of a token path is an error instead of quietly becoming its parent. Nesting under a selector list (.a, .b { .c { } }, hover { }) now joins every part. A backslash escapes a quote in a value. A nested li no longer gets a did you mean link warning. An unknown @layer name and radius: xl with no token now warn instead of failing silently.",
21
+ },
22
  {
23
  version: "v0.16.2",
24
  date: "growth phase \u00b7 a second small fix",