gerillass 0.0.6 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +13 -0
  3. data/README.md +328 -0
  4. data/SKILL.md +282 -0
  5. data/gerillass.json +2713 -0
  6. data/lib/gerillass/engine.rb +55 -0
  7. data/lib/gerillass/jekyll.rb +13 -0
  8. data/lib/gerillass.rb +26 -0
  9. data/scss/_gerillass.scss +15 -0
  10. data/scss/internal/_color-problem.scss +99 -0
  11. data/scss/internal/_color-stops-problem.scss +94 -0
  12. data/scss/internal/_condition-width.scss +22 -0
  13. data/scss/internal/_custom-property-in.scss +25 -0
  14. data/scss/internal/_gradient.scss +425 -0
  15. data/scss/internal/_image-value.scss +68 -0
  16. data/scss/internal/_is-condition-value.scss +94 -0
  17. data/scss/internal/_is-css-function.scss +21 -0
  18. data/scss/internal/_is-size.scss +22 -0
  19. data/scss/internal/_keyword-value.scss +76 -0
  20. data/scss/internal/_length-problem.scss +127 -0
  21. data/scss/internal/_sibling-index.scss +51 -0
  22. data/scss/internal/_words.scss +35 -0
  23. data/scss/library/_adaptive.scss +31 -0
  24. data/scss/library/_after.scss +28 -0
  25. data/scss/library/_all-buttons.scss +21 -0
  26. data/scss/library/_all-text-inputs.scss +21 -0
  27. data/{core → scss}/library/_antialias.scss +5 -1
  28. data/scss/library/_aspect-ratio.scss +27 -0
  29. data/scss/library/_background-dots.scss +104 -0
  30. data/scss/library/_background-image.scss +89 -0
  31. data/scss/library/_background-stripes.scss +88 -0
  32. data/scss/library/_before.scss +28 -0
  33. data/scss/library/_border-box.scss +30 -0
  34. data/scss/library/_border-radius.scss +164 -0
  35. data/scss/library/_brand-logo.scss +33 -0
  36. data/scss/library/_breakpoint.scss +96 -0
  37. data/scss/library/_breakpointer.scss +40 -0
  38. data/scss/library/_center.scss +19 -0
  39. data/scss/library/_circle.scss +15 -0
  40. data/{core → scss}/library/_clearfix.scss +1 -1
  41. data/scss/library/_columnizer.scss +125 -0
  42. data/scss/library/_container-query.scss +119 -0
  43. data/scss/library/_container.scss +72 -0
  44. data/scss/library/_counter.scss +59 -0
  45. data/scss/library/_ellipsis.scss +87 -0
  46. data/scss/library/_escape-to-parent.scss +12 -0
  47. data/scss/library/_except.scss +71 -0
  48. data/scss/library/_focus-ring.scss +63 -0
  49. data/scss/library/_font-face.scss +240 -0
  50. data/scss/library/_gradient.scss +31 -0
  51. data/scss/library/_hide.scss +25 -0
  52. data/scss/library/_index.scss +57 -0
  53. data/scss/library/_line-clamp.scss +38 -0
  54. data/scss/library/_loadify.scss +62 -0
  55. data/scss/library/_motion-safe.scss +28 -0
  56. data/scss/library/_only.scss +73 -0
  57. data/scss/library/_placeholder-shown.scss +7 -0
  58. data/scss/library/_placeholder.scss +19 -0
  59. data/scss/library/_position.scss +72 -0
  60. data/scss/library/_remove.scss +40 -0
  61. data/scss/library/_reset-css.scss +57 -0
  62. data/scss/library/_reset-figure.scss +10 -0
  63. data/scss/library/_resizable.scss +34 -0
  64. data/scss/library/_responsive-image.scss +6 -0
  65. data/scss/library/_scissors.scss +34 -0
  66. data/scss/library/_screen-agent.scss +34 -0
  67. data/scss/library/_sizer.scss +15 -0
  68. data/scss/library/_smartphone.scss +29 -0
  69. data/scss/library/_sprite.scss +100 -0
  70. data/scss/library/_stretched-link.scss +21 -0
  71. data/scss/library/_tablet.scss +29 -0
  72. data/scss/library/_text-gradient.scss +48 -0
  73. data/scss/library/_text-image.scss +15 -0
  74. data/scss/library/_text-selection.scss +22 -0
  75. data/scss/library/_text-shadow.scss +145 -0
  76. data/scss/library/_text-stroke.scss +43 -0
  77. data/scss/library/_tokens.scss +111 -0
  78. data/scss/library/_triangle.scss +115 -0
  79. data/scss/lists/_index.scss +8 -0
  80. data/scss/lists/_list-of-anchor-pseudo-classes.scss +9 -0
  81. data/scss/lists/_list-of-buttons.scss +8 -0
  82. data/scss/lists/_list-of-counter-styles.scss +59 -0
  83. data/scss/lists/_list-of-directions.scss +12 -0
  84. data/scss/lists/_list-of-length-units.scss +23 -0
  85. data/scss/lists/_list-of-text-inputs.scss +20 -0
  86. data/scss/maps/_index.scss +8 -0
  87. data/scss/maps/_map-for-breakpoints.scss +9 -0
  88. data/scss/maps/_map-for-directions.scss +12 -0
  89. data/scss/maps/_map-for-font-formats.scss +9 -0
  90. data/scss/maps/_map-for-positions.scss +13 -0
  91. data/scss/maps/_map-for-smartphones.scss +72 -0
  92. data/scss/maps/_map-for-tablets.scss +32 -0
  93. data/scss/utilities/_clear-unit.scss +11 -0
  94. data/scss/utilities/_clear-whitespace.scss +15 -0
  95. data/scss/utilities/_convert-to-em.scss +16 -0
  96. data/scss/utilities/_convert-to-number.scss +45 -0
  97. data/scss/utilities/_fill-nulls.scss +18 -0
  98. data/scss/utilities/_fluid.scss +76 -0
  99. data/scss/utilities/_font-sizer.scss +19 -0
  100. data/scss/utilities/_font-source.scss +23 -0
  101. data/scss/utilities/_gradient-value.scss +24 -0
  102. data/scss/utilities/_index.scss +26 -0
  103. data/scss/utilities/_is-color.scss +16 -0
  104. data/scss/utilities/_is-gutter.scss +24 -0
  105. data/scss/utilities/_is-number.scss +11 -0
  106. data/scss/utilities/_is-time.scss +14 -0
  107. data/scss/utilities/_map-deep-get.scss +10 -0
  108. data/scss/utilities/_pixelify.scss +20 -0
  109. data/scss/utilities/_pseudo-selector.scss +12 -0
  110. data/scss/utilities/_remify.scss +16 -0
  111. data/scss/utilities/_shade.scss +10 -0
  112. data/scss/utilities/_shorthand-property.scss +17 -0
  113. data/scss/utilities/_tint.scss +10 -0
  114. data/scss/utilities/_validate-breakpoint.scss +41 -0
  115. data/scss/utilities/_validate-length.scss +37 -0
  116. data/scss/utilities/_validate-ratio.scss +77 -0
  117. data/scss/utilities/_validate-scissors.scss +34 -0
  118. metadata +131 -46
  119. data/core/_gerillass.scss +0 -4
  120. data/core/library/_before.scss +0 -18
  121. data/core/library/_border-box.scss +0 -26
@@ -0,0 +1,425 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:list";
4
+ @use "sass:map";
5
+ @use "sass:math";
6
+ @use "sass:meta";
7
+ @use "sass:string";
8
+ @use "../maps/map-for-directions" as *;
9
+ @use "../maps/map-for-positions" as *;
10
+ @use "color-stops-problem" as *;
11
+ @use "is-condition-value" as *;
12
+ @use "is-css-function" as *;
13
+ @use "words" as *;
14
+
15
+ // Shared by the mixins and not public API; see _is-css-function.scss.
16
+ //
17
+ // Builds a linear, radial or conic gradient, plain or repeating, with an
18
+ // optional colour interpolation space, and checks every part of it. The
19
+ // gradient mixin, the gradientValue function, text-gradient, background-image,
20
+ // background-dots and background-stripes all write their gradients through it.
21
+ //
22
+ // What Chrome 152 kept and dropped, checked with CSS.supports in
23
+ // background-image:
24
+ //
25
+ // - a linear direction: an angle in deg, grad, rad or turn, a unitless 0, calc(),
26
+ // and `to` with one side or a side and a corner in either order (`to left
27
+ // top`); `to center`, `to top bottom` and a length were dropped;
28
+ // - a conic start: `from` an angle or 0, dropped with a length; a conic position
29
+ // takes `at` and a position like radial, and a size or shape dropped it;
30
+ // - a conic stop: an angle, a percentage, 0 or calc(), dropped with a length;
31
+ // - `in` a space: srgb, srgb-linear, display-p3, display-p3-linear, a98-rgb,
32
+ // prophoto-rgb, rec2020, lab, oklab, xyz, xyz-d50 and xyz-d65, or a polar
33
+ // hsl, hwb, lch or oklch with an optional `shorter`, `longer`, `increasing` or
34
+ // `decreasing hue`; a hue method on a rectangular space, `hue` without a
35
+ // method, a method without `hue`, and `rgb` or `hsv` were dropped. `in
36
+ // var(--s)` with `--s: oklab` computed to `in oklab`.
37
+ //
38
+ // The shape, size and position checks came from radial-gradient, removed in
39
+ // 3.0.0, with the measurements behind them.
40
+
41
+ $-types: "linear" "radial" "conic";
42
+ $-rectangular-spaces: "srgb" "srgb-linear" "display-p3" "display-p3-linear" "a98-rgb" "prophoto-rgb" "rec2020" "lab" "oklab" "xyz" "xyz-d50" "xyz-d65";
43
+ $-polar-spaces: "hsl" "hwb" "lch" "oklch";
44
+ $-hue-methods: "shorter" "longer" "increasing" "decreasing";
45
+ $-sides: "top" "bottom" "left" "right";
46
+
47
+ $-extents: "closest-side" "closest-corner" "farthest-side" "farthest-corner";
48
+ $-places: "left" "center" "right" "top" "bottom";
49
+
50
+ // "extent", "length" or "percentage" for a word that can size the gradient,
51
+ // or null.
52
+ @function -size-kind($word) {
53
+ $lower: string.to-lower-case("#{$word}");
54
+ @if list.index($-extents, $lower) {
55
+ @return "extent";
56
+ }
57
+ @if string.index($lower, "-") == 1 {
58
+ @return null;
59
+ }
60
+ @if isConditionValue($word, "length") {
61
+ @return "length";
62
+ }
63
+ @if isConditionValue($word, "length-percentage") {
64
+ @return "percentage";
65
+ }
66
+ @return null;
67
+ }
68
+
69
+ // Checks the size words before `at`. $shape is the shape named, or null.
70
+ @function -size-problem($words, $shape) {
71
+ $extents: 0;
72
+ $lengths: 0;
73
+ $percentages: 0;
74
+ @each $word in $words {
75
+ $kind: -size-kind($word);
76
+ @if $kind == null {
77
+ @return "`#{$word}` is not a shape, an extent keyword such as `closest-side`, or a length";
78
+ } @else if $kind == "extent" {
79
+ $extents: $extents + 1;
80
+ } @else if $kind == "length" {
81
+ $lengths: $lengths + 1;
82
+ } @else {
83
+ $percentages: $percentages + 1;
84
+ }
85
+ }
86
+ $sizes: $lengths + $percentages;
87
+ @if $extents > 1 or ($extents == 1 and $sizes > 0) {
88
+ @return "it gives the size twice";
89
+ }
90
+ @if $sizes > 2 {
91
+ @return "a size is one length or two";
92
+ }
93
+ @if $sizes == 1 and ($percentages == 1 or $shape == "ellipse") {
94
+ @return "one size is a circle's radius, which is a length and not a percentage";
95
+ }
96
+ @if $sizes == 2 and $shape == "circle" {
97
+ @return "a circle takes one length, not two";
98
+ }
99
+ @return null;
100
+ }
101
+
102
+ @function -shape-problem($shape) {
103
+ @if string.index($shape, "(") {
104
+ @return null;
105
+ }
106
+ $named: null;
107
+ $sizes: ();
108
+ @each $word in wordsOf($shape) {
109
+ $lower: string.to-lower-case($word);
110
+ @if list.index("circle" "ellipse", $lower) {
111
+ @if $named {
112
+ @return "it names the shape twice";
113
+ }
114
+ $named: $lower;
115
+ } @else {
116
+ $sizes: list.append($sizes, $word);
117
+ }
118
+ }
119
+ @return -size-problem($sizes, $named);
120
+ }
121
+
122
+ @function -position-problem($position) {
123
+ @if map.has-key($map-for-positions, $position) or string.index($position, "(") {
124
+ @return null;
125
+ }
126
+ $sizes: ();
127
+ $places: ();
128
+ $after-at: false;
129
+ @each $word in wordsOf($position) {
130
+ $lower: string.to-lower-case($word);
131
+ @if $lower == "at" {
132
+ @if $after-at {
133
+ @return "it has `at` twice";
134
+ }
135
+ $after-at: true;
136
+ } @else if $after-at {
137
+ $places: list.append($places, $word);
138
+ } @else if list.index("circle" "ellipse", $lower) {
139
+ @return "the shape goes in $shape, not $position";
140
+ } @else {
141
+ $sizes: list.append($sizes, $word);
142
+ }
143
+ }
144
+ $problem: -size-problem($sizes, null);
145
+ @if $problem {
146
+ @return "#{$problem}. Pass a position name such as `center` or `top-left`, an extent keyword such as `closest-side`, or `at` followed by a position, such as `at 30% 40%`";
147
+ }
148
+ @if $after-at and list.length($places) == 0 {
149
+ @return "`at` needs a position after it, such as `at center`";
150
+ }
151
+ @if list.length($places) > 4 {
152
+ @return "a position takes at most four values";
153
+ }
154
+ $keywords: 0;
155
+ @each $word in $places {
156
+ @if list.index($-places, string.to-lower-case($word)) {
157
+ $keywords: $keywords + 1;
158
+ } @else if not isConditionValue($word, "length-percentage") {
159
+ @return "`#{$word}` is not a position; after `at` pass left, center, right, top, bottom or a length";
160
+ }
161
+ }
162
+ @if $keywords > 2 and $keywords == list.length($places) {
163
+ @return "a position of keywords alone takes at most two, such as `at top left`";
164
+ }
165
+ @return null;
166
+ }
167
+
168
+ // True for var(), env() or attr(), which may stand for anything, so it is
169
+ // written as it is.
170
+ @function -is-substitution($value) {
171
+ @return isCssFunction($value, "var" "env" "attr");
172
+ }
173
+
174
+ // True for an angle, a unitless 0, a calculation or var().
175
+ @function -is-angle($value) {
176
+ @return $value == 0 or isConditionValue($value, "angle") or -is-substitution($value);
177
+ }
178
+
179
+ // The CSS for a linear gradient's $direction, or null when a browser would
180
+ // drop it. background-image checks its $filter-direction with this.
181
+ @function gradientDirection($direction) {
182
+ @if map.has-key($map-for-directions, $direction) {
183
+ @return map.get($map-for-directions, $direction);
184
+ }
185
+ @if -is-angle($direction) {
186
+ @return $direction;
187
+ }
188
+ $words: wordsOf($direction);
189
+ @if meta.type-of($direction) == "string" and string.to-lower-case("#{list.nth($words, 1)}") == "to" and list.length($words) >= 2 and list.length($words) <= 3 {
190
+ $vertical: 0;
191
+ $horizontal: 0;
192
+ @for $i from 2 through list.length($words) {
193
+ $word: string.to-lower-case("#{list.nth($words, $i)}");
194
+ @if $word == "top" or $word == "bottom" {
195
+ $vertical: $vertical + 1;
196
+ } @else if $word == "left" or $word == "right" {
197
+ $horizontal: $horizontal + 1;
198
+ } @else {
199
+ @return null;
200
+ }
201
+ }
202
+ @if $vertical <= 1 and $horizontal <= 1 {
203
+ @return string.unquote($direction);
204
+ }
205
+ }
206
+ @return null;
207
+ }
208
+
209
+ // What is wrong with $in, or null.
210
+ @function -in-problem($in) {
211
+ // Measured in Chrome 152: `in var(--s)` with `--s: oklab` computed to
212
+ // `in oklab`, so var() is written as it is.
213
+ @if -is-substitution($in) {
214
+ @return null;
215
+ }
216
+ @if not list.index("string" "list", meta.type-of($in)) {
217
+ @return "it is not a colour space";
218
+ }
219
+ $words: ();
220
+ @each $word in wordsOf($in) {
221
+ $words: list.append($words, string.to-lower-case("#{$word}"));
222
+ }
223
+ $space: list.nth($words, 1);
224
+ $count: list.length($words);
225
+ @if list.index($-rectangular-spaces, $space) {
226
+ @if $count > 1 {
227
+ @return "a hue method such as `longer hue` needs a polar space: hsl, hwb, lch or oklch";
228
+ }
229
+ @return null;
230
+ }
231
+ @if list.index($-polar-spaces, $space) {
232
+ @if $count == 1 {
233
+ @return null;
234
+ }
235
+ @if $count == 3 and list.index($-hue-methods, list.nth($words, 2)) and list.nth($words, 3) == "hue" {
236
+ @return null;
237
+ }
238
+ @return "after a polar space comes a hue method and the word hue, such as `oklch longer hue`";
239
+ }
240
+ @return "`#{$space}` is not a colour space; pass one such as oklab or oklch";
241
+ }
242
+
243
+ // What is wrong with a conic gradient's stops, or null. A position there is an
244
+ // angle or a percentage, so each is checked for that and then replaced with 0,
245
+ // and the list goes through colorStopsProblem for everything else.
246
+ @function -conic-stops-problem($colors) {
247
+ $checked: ();
248
+ @each $item in $colors {
249
+ @if meta.type-of($item) == "list" and list.length($item) > 1 {
250
+ $stop: (list.nth($item, 1),);
251
+ @for $i from 2 through list.length($item) {
252
+ $position: list.nth($item, $i);
253
+ @if not -is-conic-position($position) {
254
+ @return "in `#{meta.inspect($item)}`, `#{meta.inspect($position)}` is not an angle or a percentage, and a conic stop is placed by one";
255
+ }
256
+ @if -is-substitution($position) {
257
+ $stop: list.append($stop, $position, space);
258
+ } @else {
259
+ $stop: list.append($stop, 0, space);
260
+ }
261
+ }
262
+ $checked: list.append($checked, $stop, comma);
263
+ } @else if meta.type-of($item) == "number" or isConditionValue($item, "length-percentage") or isConditionValue($item, "angle") {
264
+ @if not -is-conic-position($item) {
265
+ @return "`#{meta.inspect($item)}` is not an angle or a percentage, and a conic hint is placed by one";
266
+ }
267
+ $checked: list.append($checked, 0, comma);
268
+ } @else {
269
+ $checked: list.append($checked, $item, comma);
270
+ }
271
+ }
272
+ @return colorStopsProblem($checked);
273
+ }
274
+
275
+ @function -is-conic-position($value) {
276
+ @if -is-angle($value) or meta.type-of($value) == "calculation" {
277
+ @return true;
278
+ }
279
+ @if meta.type-of($value) == "number" {
280
+ @return math.unit($value) == "%";
281
+ }
282
+ $text: string.unquote("#{$value}");
283
+ @return string.slice($text, -1) == "%" and isConditionValue($value, "length-percentage");
284
+ }
285
+
286
+
287
+ // A gradient, as a value. $member, $colors-name and $direction-name are the
288
+ // names an error gives, so a mixin that passes its own arguments through names
289
+ // them as its user wrote them. $warn is false for the fallback a mixin writes
290
+ // before a gradient with $in, so a single colour warns once.
291
+ @function gradientOf(
292
+ $colors,
293
+ $type: linear,
294
+ $direction: null,
295
+ $shape: null,
296
+ $position: null,
297
+ $from: null,
298
+ $in: null,
299
+ $repeating: false,
300
+ $member: "gradient",
301
+ $colors-name: "$colors",
302
+ $direction-name: "$direction",
303
+ $warn: true
304
+ ) {
305
+ $kind: string.to-lower-case("#{$type}");
306
+ @if meta.type-of($type) != "string" or not list.index($-types, $kind) {
307
+ @error "`#{meta.inspect($type)}` is not a valid $type for `#{$member}`. Pass linear, radial or conic.";
308
+ }
309
+ @if meta.type-of($repeating) != "bool" {
310
+ @error "`#{meta.inspect($repeating)}` is not a valid $repeating for `#{$member}`. Pass true or false.";
311
+ }
312
+
313
+ // An argument the type does not read would be dropped without a word.
314
+ @if $direction != null and $kind != "linear" {
315
+ $hint: "Use $position for where a radial gradient sits.";
316
+ @if $kind == "conic" {
317
+ $hint: "Use $from for where a conic gradient starts.";
318
+ }
319
+ @error "#{$direction-name} is for a linear gradient, and this one is #{$kind}. #{$hint}";
320
+ }
321
+ @if $shape != null and $kind != "radial" {
322
+ @error "$shape is for a radial gradient, and this one is #{$kind}.";
323
+ }
324
+ @if $position != null and $kind == "linear" {
325
+ @error "$position is for a radial or conic gradient, and this one is linear. Use #{$direction-name}.";
326
+ }
327
+ @if $from != null and $kind != "conic" {
328
+ @error "$from is for a conic gradient, and this one is #{$kind}.";
329
+ }
330
+
331
+ $prelude: ();
332
+
333
+ @if $kind == "linear" and $direction != null {
334
+ $value: gradientDirection($direction);
335
+ @if $value == null {
336
+ @error "`#{meta.inspect($direction)}` is not a valid #{$direction-name} for `#{$member}`. Pass an angle such as `45deg` or `0.25turn`, `\"to top right\"`, or one of: #{map.keys($map-for-directions)}.";
337
+ }
338
+ $prelude: list.append($prelude, $value, space);
339
+ }
340
+
341
+ // A shape or position written without quotes, such as `ellipse 70% 60%` or
342
+ // `at 50% 0%`, reaches here as a space-separated list; it is the same words.
343
+ @if meta.type-of($shape) == "list" and list.separator($shape) == "space" {
344
+ $shape: "#{$shape}";
345
+ }
346
+ @if meta.type-of($position) == "list" and list.separator($position) == "space" {
347
+ $position: "#{$position}";
348
+ }
349
+
350
+ @if $kind == "radial" and $shape != null {
351
+ @if meta.type-of($shape) != "string" {
352
+ @error "`#{meta.inspect($shape)}` is not a valid $shape for `#{$member}`. Pass circle or ellipse, optionally with a size.";
353
+ }
354
+ $shape: string.unquote($shape);
355
+ $problem: -shape-problem($shape);
356
+ @if $problem {
357
+ @error "`#{$shape}` is not a valid $shape for `#{$member}`: #{$problem}. A browser drops the whole gradient.";
358
+ }
359
+ $prelude: list.append($prelude, $shape, space);
360
+ }
361
+
362
+ @if $kind == "conic" and $from != null {
363
+ @if not -is-angle($from) {
364
+ @error "`#{meta.inspect($from)}` is not a valid $from for `#{$member}`. Pass an angle, such as `45deg` or `0.25turn`.";
365
+ }
366
+ $prelude: list.append($prelude, string.unquote("from #{$from}"), space);
367
+ }
368
+
369
+ @if $position != null {
370
+ @if meta.type-of($position) != "string" {
371
+ @error "`#{meta.inspect($position)}` is not a valid $position for `#{$member}`. Pass a position name such as `center` or `top-left`, or a CSS position as a string, such as `\"at 30% 40%\"`.";
372
+ }
373
+ $position: string.unquote($position);
374
+ @if map.has-key($map-for-positions, $position) {
375
+ $prelude: list.append($prelude, map.get($map-for-positions, $position), space);
376
+ } @else {
377
+ $problem: -position-problem($position);
378
+ @if not $problem and $kind == "conic" and not string.index($position, "(") and string.to-lower-case(list.nth(wordsOf($position), 1)) != "at" {
379
+ $problem: "a conic gradient has no size, so its position starts with `at`";
380
+ }
381
+ @if $problem {
382
+ @error "`#{$position}` is not a valid $position for `#{$member}`: #{$problem}. A browser drops the whole gradient.";
383
+ }
384
+ $prelude: list.append($prelude, $position, space);
385
+ }
386
+ }
387
+
388
+ $problem: null;
389
+ @if $kind == "conic" {
390
+ $problem: -conic-stops-problem($colors);
391
+ } @else {
392
+ $problem: colorStopsProblem($colors);
393
+ }
394
+ @if $problem {
395
+ @error "`#{meta.inspect($colors)}` is not a valid #{$colors-name} for `#{$member}`: #{$problem}. A browser drops the whole gradient.";
396
+ }
397
+ // From the compat data, not measured in an old browser: a gradient with one
398
+ // colour is kept only from Chrome 135, Firefox 136 and Safari 18.4.
399
+ @if $warn and list.length($colors) == 1 and not -is-substitution(list.nth($colors, 1)) {
400
+ @warn "`#{$member}` was given one colour, `#{meta.inspect($colors)}`. Browsers before Chrome 135, Firefox 136 and Safari 18.4 drop a gradient with a single colour stop; pass two colours, or use background-color.";
401
+ }
402
+
403
+ @if $in != null {
404
+ $problem: -in-problem($in);
405
+ @if $problem {
406
+ @error "`#{meta.inspect($in)}` is not a valid $in for `#{$member}`: #{$problem}. A browser drops the whole gradient.";
407
+ }
408
+ $prelude: list.append($prelude, string.unquote("in #{$in}"), space);
409
+ }
410
+
411
+ $stops: ();
412
+ @each $item in $colors {
413
+ $stops: list.append($stops, $item, comma);
414
+ }
415
+
416
+ $name: "#{$kind}-gradient";
417
+ @if $repeating {
418
+ $name: "repeating-#{$name}";
419
+ }
420
+ $function: meta.get-function($name, $css: true);
421
+ @if list.length($prelude) > 0 {
422
+ @return meta.call($function, $prelude, $stops...);
423
+ }
424
+ @return meta.call($function, $stops...);
425
+ }
@@ -0,0 +1,68 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:meta";
4
+ @use "sass:string";
5
+ @use "is-css-function" as *;
6
+
7
+ // Shared by the mixins and not public API; see _is-css-function.scss.
8
+
9
+ // An image path goes into url(). A var(), url(), image-set() or gradient is
10
+ // already an image, and wrapping it produced url(var(--x)), which loads nothing.
11
+ //
12
+ // $unquote is there because the copies this replaced differed:
13
+ // background-dots and background-stripes wrote `url(a.png)`, the other three
14
+ // `url("a.png")`. Both load the same file, and keeping each as it was means no
15
+ // stylesheet changes.
16
+ //
17
+ // What it refuses, measured in Chrome 152:
18
+ //
19
+ // - a list where one image is meant: `url(16 9)` was dropped;
20
+ // - a number, a colour or a boolean: `url(42)`, `url(#ff0000)` and `url(true)`
21
+ // are kept, but ask for a file named 42, a fragment of the page, or a file
22
+ // named true, which is never the image meant. background-dots and
23
+ // background-stripes refused these already.
24
+ //
25
+ // And an unquoted path is quoted when it has to be: `url(a b)`, `url(a(1).png)`
26
+ // and `url(a'b.png)` were dropped, while the same paths in quotes were kept and
27
+ // `url("a b")` resolved to `a%20b`. Every other path is written as before.
28
+ @function imageValue($value, $mixin, $argument, $unquote: false) {
29
+ // paint(), image() and -webkit-cross-fade() were kept as images in Chrome
30
+ // 152 and used to be wrapped into url(paint(...)), which loads nothing;
31
+ // cross-fade(), element() and -moz-element() are in the specification or
32
+ // other engines.
33
+ @if isCssFunction($value, "var" "url" "image-set" "linear-gradient" "radial-gradient" "conic-gradient" "repeating-linear-gradient" "repeating-radial-gradient" "repeating-conic-gradient" "paint" "image" "cross-fade" "-webkit-cross-fade" "element" "-moz-element") {
34
+ @return $value;
35
+ }
36
+ @if meta.type-of($value) == "list" or meta.type-of($value) == "arglist" or meta.type-of($value) == "map" {
37
+ @error "`#{meta.inspect($value)}` is not a valid #{$argument} for `#{$mixin}`: it is a list where one image is meant, and a browser drops `url()` with a list in it. Pass one path as a string, such as `\"/img/hero.png\"`.";
38
+ }
39
+ @if meta.type-of($value) != "string" {
40
+ @error "`#{meta.inspect($value)}` is not a valid #{$argument} for `#{$mixin}`: an image path is a string, and `url(#{meta.inspect($value)})` asks for a file of that name, which is never the image. Pass a path as a string, such as `\"/img/hero.png\"`, or var() or a gradient.";
41
+ }
42
+ @if not $unquote {
43
+ @return url($value);
44
+ }
45
+ $text: string.unquote($value);
46
+ @if not (string.index($text, " ") or string.index($text, "(") or string.index($text, ")") or string.index($text, "'") or string.index($text, "\"")) {
47
+ @return url(string.unquote($value));
48
+ }
49
+ @if string.index($text, "\"") {
50
+ @return string.unquote("url('#{-escaped-single-quotes($text)}')");
51
+ }
52
+ @return string.unquote("url(\"#{$text}\")");
53
+ }
54
+
55
+ // A path holding a double quote goes into single quotes, so any single quote in
56
+ // it is escaped.
57
+ @function -escaped-single-quotes($text) {
58
+ $result: "";
59
+ @for $i from 1 through string.length($text) {
60
+ $character: string.slice($text, $i, $i);
61
+ @if $character == "'" {
62
+ $result: $result + "\\'";
63
+ } @else {
64
+ $result: $result + $character;
65
+ }
66
+ }
67
+ @return $result;
68
+ }
@@ -0,0 +1,94 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:list";
4
+ @use "sass:math";
5
+ @use "sass:meta";
6
+ @use "sass:string";
7
+
8
+ // Shared by the mixins and not public API; see _is-css-function.scss.
9
+
10
+ // Every length unit a width condition kept in Chrome 152, in @media and
11
+ // @container alike and in any case: `(min-width: 10PX)` matched. `x`, `dppx`,
12
+ // `fr`, `s`, `%` and a made-up unit never matched, and neither did a unitless
13
+ // number other than 0.
14
+ $-length-units: "px" "cm" "mm" "q" "in" "pt" "pc" "em" "rem" "ex" "rex" "ch" "rch" "cap" "rcap" "ic" "ric" "lh" "rlh" "vw" "vh" "vi" "vb" "vmin" "vmax" "svw" "svh" "svi" "svb" "svmin" "svmax" "lvw" "lvh" "lvi" "lvb" "lvmin" "lvmax" "dvw" "dvh" "dvi" "dvb" "dvmin" "dvmax" "cqw" "cqh" "cqi" "cqb" "cqmin" "cqmax";
15
+
16
+ // The units a resolution condition kept, also in any case. A unitless number, a
17
+ // length and a negative resolution never matched; `0dppx` was kept.
18
+ $-resolution-units: "dpi" "dpcm" "dppx" "x";
19
+
20
+ // Angles, for an oblique `font-style`: `oblique 0.2rad` was kept in a @font-face
21
+ // rule and `oblique 10`, unitless, was dropped.
22
+ $-angle-units: "deg" "grad" "rad" "turn";
23
+
24
+ // True for a value a @media or @container condition can match: a number in one
25
+ // of the units above, a calculation, or either written as a quoted string, which
26
+ // Sass writes into a condition without its quotes, so `breakpoint(min, "600px")`
27
+ // has always worked. A calculation is taken on trust: `calc(30em + 1px)`,
28
+ // `clamp()`, `min()` and `max()` were all kept, and Sass reduces `calc(10deg)`
29
+ // to a number before it gets here, so that one is checked like any other.
30
+ //
31
+ // $kind is "length" or "resolution" for a condition. A negative length was
32
+ // kept, since it is valid and simply matches every width or none; a negative
33
+ // resolution was not. Two more kinds serve values outside a condition, where a
34
+ // caller rules out a negative itself if the property does: "length-percentage",
35
+ // a length or a percentage, and "angle".
36
+ @function isConditionValue($value, $kind: "length") {
37
+ $units: $-length-units;
38
+ @if $kind == "resolution" {
39
+ $units: $-resolution-units;
40
+ } @else if $kind == "length-percentage" {
41
+ $units: list.append($-length-units, "%");
42
+ } @else if $kind == "angle" {
43
+ $units: $-angle-units;
44
+ }
45
+
46
+ @if meta.type-of($value) == "calculation" {
47
+ @return true;
48
+ }
49
+ @if meta.type-of($value) == "number" {
50
+ @if math.is-unitless($value) {
51
+ @return ($kind == "length" or $kind == "length-percentage") and $value == 0;
52
+ }
53
+ @if $kind == "resolution" and $value < 0 {
54
+ @return false;
55
+ }
56
+ @return list.index($units, string.to-lower-case(math.unit($value))) != null;
57
+ }
58
+ @if meta.type-of($value) != "string" {
59
+ @return false;
60
+ }
61
+
62
+ $text: string.to-lower-case(string.unquote($value));
63
+ @each $function in "calc(" "clamp(" "min(" "max(" {
64
+ @if string.index($text, $function) == 1 {
65
+ @return true;
66
+ }
67
+ }
68
+
69
+ // A quoted length, such as "600px": the number runs up to the first
70
+ // character that cannot be part of one, and the rest is the unit.
71
+ $unit-start: null;
72
+ $digits: 0;
73
+ @for $i from 1 through string.length($text) {
74
+ $character: string.slice($text, $i, $i);
75
+ @if $unit-start == null {
76
+ @if string.index("0123456789", $character) {
77
+ $digits: $digits + 1;
78
+ } @else if not string.index(".+-", $character) {
79
+ $unit-start: $i;
80
+ }
81
+ }
82
+ }
83
+ @if $digits == 0 {
84
+ @return false;
85
+ }
86
+ @if $unit-start == null {
87
+ // A quoted unitless number: only zero, and only for a length.
88
+ @return ($kind == "length" or $kind == "length-percentage") and not string.index($text, "1") and not string.index($text, "2") and not string.index($text, "3") and not string.index($text, "4") and not string.index($text, "5") and not string.index($text, "6") and not string.index($text, "7") and not string.index($text, "8") and not string.index($text, "9");
89
+ }
90
+ @if $kind == "resolution" and string.index(string.slice($text, 1, $unit-start - 1), "-") {
91
+ @return false;
92
+ }
93
+ @return list.index($units, string.slice($text, $unit-start)) != null;
94
+ }
@@ -0,0 +1,21 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:list";
4
+ @use "sass:meta";
5
+ @use "sass:string";
6
+
7
+ // scss/internal/ holds checks the mixins share. No _index.scss forwards it, so
8
+ // a user cannot reach these functions and they are not public API: through
9
+ // `@use "gerillass" as *` or `@import` a call to one renders as literal CSS,
10
+ // and through a namespace it is an undefined function. Their names must not
11
+ // start with `-` or `_`, which would make them private to their own file.
12
+
13
+ // True for an unquoted call to one of $names, such as var(--x). A quoted string
14
+ // stays text, so `"var(--x)"` is not a call.
15
+ @function isCssFunction($value, $names) {
16
+ @if meta.type-of($value) != "string" or string.slice(meta.inspect($value), 1, 1) == "\"" {
17
+ @return false;
18
+ }
19
+ $open: string.index($value, "(");
20
+ @return $open != null and list.index($names, string.to-lower-case(string.slice($value, 1, $open - 1))) != null;
21
+ }
@@ -0,0 +1,22 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:list";
4
+ @use "sass:meta";
5
+ @use "sass:string";
6
+
7
+ // Shared by the mixins and not public API; see _is-css-function.scss.
8
+
9
+ // A size Sass can use inside calc(): a number, a calculation such as calc(),
10
+ // min() or clamp(), or var() and env(), which only the browser can resolve.
11
+ // Anything else would pass silently through calc(): `calc(nonsense * 5)`
12
+ // compiles.
13
+ @function isSize($value) {
14
+ @if meta.type-of($value) == "number" or meta.type-of($value) == "calculation" {
15
+ @return true;
16
+ }
17
+ @if meta.type-of($value) == "string" and string.slice(meta.inspect($value), 1, 1) != "\"" {
18
+ $open: string.index($value, "(");
19
+ @return $open != null and list.index("var" "env", string.to-lower-case(string.slice($value, 1, $open - 1))) != null;
20
+ }
21
+ @return false;
22
+ }