File size: 15,887 Bytes
9da4a9b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# 8. Effects

Effects are the parts of DSSL that CSS has no equivalent for, or only a clumsy one. Each effect
has two halves:

- a **description**: the text the author writes and the record the resolver produces from it.
  This half is fully specified here and a Level 2 implementation MUST produce the same records.
- a **rendering contract**: what a Level 4 renderer must draw. Where the contract depends on
  generator code that is not yet written down as prose, this chapter says so. Those parts are
  *defined by the reference implementation* (`public/dssl/texture.js`, `aura.js`, `slider.js`)
  until a prose definition replaces them. They are marked **Pending**.

All vocabulary tables (setting names, ranges, presets) are generated into appendix A.

## 8.1 Texture

`texture:` lays a generated image on top of an element's own background.

```dssl
.card   { background: #c9a66b; texture: wood }
.sheet  { background: linear-gradient(135deg, #ff7a00, #7a1fff); texture: grain intensity 0.6 }
.poster { background: #e9e2d0; texture: paper, scanlines intensity 0.1 }
```

**Description.**

- **FX-1.** The value is `none`, or a comma-separated list of layers. `none` writes
  `@texture: null`. An empty value is the error `texture needs a name`. Any error anywhere in
  the value drops the whole declaration.
- **FX-2.** Each layer starts with a preset name or a generator name (appendix A). Otherwise
  the error is `unknown texture "<w>"`, with a did-you-mean over presets and generators.
- **FX-3.** After the name come settings. A setting is a key from the texture settings table
  (appendix A), followed by as many values as that key takes. A key has one *kind*:
  `flag` (no value), `number`, `numbers` (several), `word` (one of a list) or `color`.
  - A number outside the key's range is the error `texture "<key>" must be between <min> and
    <max>`. A non-number is `texture setting "<key>" needs a number, got "<v>"`.
  - A word not in the list is `unknown <key> "<w>"` with a did-you-mean.
  - A missing value is `texture setting "<key>" needs a value` (or `N values`). A value that is
    itself a setting key counts as missing.
  - A color value is resolved by chapter 3 (tokens, alpha and all). A bad color drops the
    declaration.
  - An unknown key is `unknown texture setting "<key>"` with a did-you-mean.
  - A key marked *optional* (`symmetrical`) may stand alone, and then takes its first listed
    value.
- **FX-4.** A preset is a stored list of layers. Writing a preset name gives those layers.
  Settings apply to every layer of the preset, except that `color` and `color2` apply only to
  the first layer when the preset has several. A generator name gives one layer made of the
  generator's defaults.
- **FX-5.** The output is `@texture` with the list of layer records, each being the preset or
  generator layer with the author's overrides applied, and a `preset` field naming the preset
  (null for a bare generator).

**Rendering contract.**

- **FX-6.** The texture is drawn above the element's own background and below its content. The
  author's background (a color, a gradient, or several layers, written as `background` or
  `background-image`) is never replaced, and removing the texture gives back exactly the
  original.
- **FX-7.** A texture is deterministic. The same layer record, including `seed`, MUST give the
  same pixels on every machine and every engine. The tile MUST repeat without a visible seam.
  (Why: the reference implementation draws from `seed` and plain arithmetic only.)
- **FX-8.** `symmetrical` mirrors every other tile on the chosen axis (`x`, `y`, or both), so
  the pattern repeats twice as far apart and each tile edge meets its own reflection. It applies
  to textures made as images (noise, bump, rings, waves, hex) and is ignored by the pure
  pattern generators (lines, dots, grid, checker, carbon).
- **FX-9.** `animate N` steps the texture every `N` ms. It stands still when the user prefers
  reduced motion.
- **FX-10.** Colors given as tokens follow the token, including under a theme change.

**Pending.** The generators (`noise`, `bump`, `lines`, `dots`, `grid`, `checker`, `weave`,
`carbon`, `waves`, `hex`, `rings`) and the seeded random number generator are defined by
`texture.js`. A prose definition of each is the first item of the Level 4 work.

## 8.2 Aura

An aura is light and particles around an element. There are no presets. Fire, snow and sparks
are written by the author, usually as a `behavior`.

**Description.**

- **FX-11.** The properties are `aura`, `aura-glow`, `aura-particle` (and `aura-particles`),
  `glow-particle` (and `-particles`), `aura-glow-particle` (and `-particles`). They write two
  slots, `@aura-glow` and `@aura-particles`. A property that does not touch a slot leaves it
  out of its output.

  | Property | Writes |
  |---|---|
  | `aura` | both slots, as `glow ..., particles ...` clauses; it replaces both |
  | `aura-glow` | the glow slot |
  | `aura-particle(s)` | the particles slot |
  | `glow-particle(s)` | the particles slot, with every particle glowing |
  | `aura-glow-particle(s)` | both slots, with shared settings; both a glow and particles are required |

- **FX-12.** `none` clears exactly the slots the property owns (`aura-glow: none` leaves the
  particles alone). An empty value is the error `<prop> needs a value`. An empty list writes
  `null` for the slot.
- **FX-13.** A value is a comma-separated list of layers. In `aura`, each layer starts with
  `glow`, `particles` or `particle`, otherwise the error
  `an aura layer starts with glow or particles, not "<w>"`.
- **FX-14.** Inside a layer, the first color (or `->`-joined color list) and the first number
  are positional: for a glow they are the color and the radius, for particles they are the
  colors and the count. Everything else is a named setting from the glow or particle table
  (appendix A). A leftover or unknown word is an error, with a did-you-mean.
- **FX-15.** The output is `@aura-glow` and/or `@aura-particles`, each a list of layer records
  or `null`.

**Rendering contract.** (From the reference implementation. All of it is **Pending** as prose.)

- **FX-16.** The glow is drawn only outside the element's box. `behind` does the same for
  particles.
- **FX-17.** An element that is off screen, hidden, clipped away by an overflow container, or
  removed emits nothing. When no aura is active, the renderer stops its loop.
- **FX-18.** At most 1800 particles are alive across the page. When requests exceed that, every
  emitter gets a fair share.
- **FX-19.** Under reduced motion, glows stand still and particles are not emitted, unless the
  layer says `essential`.
- **FX-20.** A state block can change the aura. Leaving the state lets live particles finish,
  then the emitter goes.
- **FX-21.** `from thumb` makes particles leave a range slider's thumb at the current value.

## 8.3 Slider

`slider:` styles `<input type="range">` from one description. Without it, every engine needs
its own vendor pseudo-elements.

```dssl
input.volume {
  slider: track 6 color.line radius full, fill color.accent,
          thumb 24 color.paper, ring 3 color.accent, glow color.accent 14,
          ticks 10 color.muted
}
```

**Description.**

- **FX-22.** The value is a comma-separated list of clauses. A clause starts with one of
  `track`, `fill`, `thumb`, `shape`, `ring`, `glow`, `ticks`, `graph`, `graph-height`,
  `graph-gap`. An unknown head is `unknown slider setting "<w>"` with a did-you-mean. An empty
  value is `slider needs a description`.
- **FX-23.** Every word after a clause's head MUST be used by that clause. A leftover is the
  error `unexpected "<words>" in the <head> setting`, with a hint about the missing comma.
- **FX-24.** The default record is: track height 6, color `var(--dssl-color-line)`, radius
  `full`; fill `var(--dssl-color-accent)`; thumb size 22, color `var(--dssl-color-accent)`;
  shape `circle`; no ring, no glow, no ticks, no graph; graph height 40, graph gap 2. Clauses
  change the record in order.
- **FX-25.** Clause forms and limits are in appendix A. The notable ones: `ticks` needs a count
  of at least 2, `graph` needs 2 to 120 numbers each in 0 to 100, `graph-height` is at least 8,
  `graph-gap` is not negative.
- **FX-26.** The output is `@slider` with the record. When the rule is emitted (5.4), the
  resolver turns the record into CSS: base declarations on the element, put *first* in the rule
  so the author's own declarations in the same rule win, plus one shadow rule per vendor
  pseudo-element. That CSS is defined by `sliderCss`. **Pending.**

**Rendering contract.**

- **FX-27.** The fill and the graph bars follow the value, which CSS cannot know. The renderer
  keeps `--dssl-p` (the value as 0 to 1) up to date on the input, for typing, dragging, arrow
  keys and programmatic changes.
- **FX-28.** A native engine that styles the slider parts itself does not need the vendor
  pseudo-elements. It MUST give the same appearance as the record describes.

## 8.4 Glass and tilt

```dssl
.pane {
  glass: 18 saturate 1.6
  tilt: 10 lift 1.04 glare
}
```

**Glass (plain CSS).**

- **FX-29.** `glass: words` or `none`. `none` writes `backdrop-filter: none`. Otherwise the
  words are: a number (blur in px, default 16, not negative), `saturate N` (default 1.5),
  `bright N`, `tint <color>` or `tint none`, and a bare color word counts as a tint. A word
  that is none of these is `unknown glass setting "<w>"`. A missing number is
  `<word> needs a number`. A color is recognised as a bare tint when it starts with `#`, `rgb`,
  `hsl`, `oklch`, `oklab` or `color`, contains `/`, looks like `name.key`, or is `white`,
  `black` or `transparent`.
- **FX-30.** The output is `backdrop-filter: blur(Bpx) saturate(S)` followed by
  ` brightness(R)` when given. Then `background-color: <tint>` unless the tint is `none`, where
  the default tint is `color-mix(in srgb, currentColor 8%, transparent)`. The author's own
  `background` written after `glass:` replaces the wash.

**Tilt (needs the pointer).**

- **FX-31.** `tilt: words` or `none`. The words are a number (max degrees, 0 to 45, default 8),
  `lift N` (scale on hover, above 0), `perspective N` (px, at least 100, default 900),
  `speed N` (0.02 to 1, default 0.18), `invert` and `glare`. The record is
  `{ max, lift, perspective, speed, invert, glare }`. `none` writes `@tilt: null`. An empty value
  is `tilt needs a number of degrees`. Out-of-range values are errors that name the range.
- **FX-32.** The renderer adds, after the element's own `transform`:
  `perspective(<P>px) rotateX(var(--dssl-tx, 0deg)) rotateY(var(--dssl-ty, 0deg))
  scale(var(--dssl-ts, 1))`, and updates the three variables from the pointer, so that moving
  the pointer never triggers a restyle. With `glare`, it also keeps `--dssl-gx` and
  `--dssl-gy` (0% to 100%) pointing at the pointer.
- **FX-33.** Tilt ignores touch, stays flat under reduced motion, and stops when the page is
  frozen. It combines with `translate`, `scale` and `rotate`. It does not combine with `bend`
  or `fold`, because both write `transform`.

## 8.5 Deform

`bend`, `bend-y`, `bend-up`, `fold`, `stretch` and `bend-around` deform an element, or for
`bend-around` its children.

- **FX-34.** `bend`, `bend-y`, `bend-up`, `fold` take a bare number of degrees (or `Ndeg`).
  Anything else is `<name> takes a bare number of degrees`. They write `@bend`, `@bend-y`,
  `@bend-up`, `@fold` with the number.
- **FX-35.** `stretch: F [horizontal|vertical|x|y]` with `F` above 0. It writes `@stretch`
  `{ factor, axis }`, axis default `horizontal`. A bad value is
  `stretch needs a positive factor, ...`; `F <= 0` is `stretch: F would flatten the element to
  nothing`.
- **FX-36.** `bend-around: D` is a bare number of degrees between -170 and 170, otherwise
  `bend-around caps at ±170° ...`. It writes `@bend-around`.
- **FX-37.** *Composition.* The deform parts of an element combine into one transform string,
  in this order, followed by the element's own transform (when it is not `none`). `d` is the
  record of numbers, `h` the element height in px (0 when there is no box yet):
  1. if any of bend, bend-y, fold, bend-up is non-zero: `perspective(900px)`;
  2. if bend: `rotateX(<bend>deg)`; if bend-y: `rotateY(<bend-y>deg)`;
  3. if fold: with `h > 0`: `translateY(<-h/2>px) rotateX(<fold>deg) translateY(<h/2>px)`;
     without a box: `rotateX(<fold>deg)` and the origin `50% 0%`;
  4. if bend-up: with `h > 0`: `translateY(<h/2>px) rotateX(<up>deg) translateY(<-h/2>px)`;
     without a box: `rotateX(<up>deg)` and the origin `50% 100%`;
  5. if stretch is set and not 1: for the horizontal axis
     `scaleX(<f>) scaleY(<1/f rounded to 4 places>)`, for vertical
     `scaleY(<f>) scaleX(<1/f>)`.

  Translations are rounded to 2 decimal places. The result is `none` when there are no parts.
  In keyframes (7.1) there is no box, so the second form of fold and bend-up is used.
- **FX-38.** *Arc layout.* `bend-around: D` lays `n` children along an arc. It does nothing
  when `n < 2`, the container has no width, or `|D|` is too small (`sin(|D|/2) < 0.0001`).
  Otherwise let `half = |D|/2` in radians, `R = round2((width/2) / sin(half))`, and the angle of
  child `i` (from 0) is `round2(-D/2 + D*i/(n-1))` degrees. `round2(v)` is
  `round(v * 100) / 100`. A positive `D` makes a dome, negative a smile.

## 8.6 Overlap

- **FX-39.** `overlap: allow|none|deny|auto`. It writes `@overlap` with `allow` (for `none` too),
  `deny` or `auto`. Anything else is `unknown overlap policy "<v>"`. The value inherits down the
  tree.
- **FX-40.** `deny`: the element yields out of any overlap with other elements. `auto`: it
  yields only while its opacity is below 100%. `allow` is the default. How an element yields is
  defined by the reference engine's `overlapNudge`. **Pending.**

## 8.7 Fonts

`fonts { }` loads fonts. A font is a stylesheet link or an `@font-face`, and neither belongs
in a rule, so it has its own block.

```dssl
fonts {
  Fraunces: 500 700 800
  "Hanken Grotesk": 400 700 italic
  Inter: 100..900 display optional
  Brand: from "/fonts/brand.woff2" 400
}
```

- **FX-41.** The body is verbatim text (2.7). Block comments are blanked, and a `//` comment
  that starts a line or follows white space is removed. Each remaining line has a trailing `;`
  removed and is `Family: words`. The family is everything before the first colon outside
  quotes, unquoted, with runs of white space collapsed to one space. A line with no colon is
  `expected "Family: weights" in "<text>"`.
- **FX-42.** The words are: weights (`400`, or a range `100..900`, each from 1 to 1000, a range
  rising), `italic`, `display <swap|block|fallback|optional|auto>`, `from "<file>"`. Anything
  else is `unknown font setting "<w>"`.
- **FX-43.** Without `from`, the font is from Google Fonts. All Google families together make
  one link:
  `https://fonts.googleapis.com/css2?family=<parts>&display=<d>` where each part is
  `Name:wght@400;700` (sorted by number, spaces in the name as `+`), or `Name:ital,wght@0,400;
  0,700;1,400;1,700` with italics (all weights, upright first), or `Name` alone for no weights,
  or `Name:ital@0;1` for italics with no weights. The `display` is the last one given on any
  Google line, default `swap`. The link records the line of the `fonts` block.
- **FX-44.** With `from`, the line makes an `@font-face` block (as a `raw` entry, so it goes into
  the page stylesheet) with `font-family`, `src: url("<file>")` plus `format("woff2"|"woff"|
  "truetype"|"opentype")` by extension, `font-weight` (a range written with a space),
  `font-style` and `font-display` (default `swap`). A `from` line takes at most one weight.
- **FX-45.** Google's servers see every visitor who loads one of their fonts. Authors who need
  to avoid that use `from`.