css-utility-functions 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.css ADDED
@@ -0,0 +1,897 @@
1
+ /**
2
+ * CSS Utility Functions
3
+ *
4
+ * A collection of reusable CSS custom functions using the @function syntax.
5
+ * Import this file to use all utility functions in your project.
6
+ *
7
+ * ⚠️ Note: CSS @function is experimental. Check browser support before production use.
8
+ *
9
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@function
10
+ */
11
+
12
+ /* Color Utilities */
13
+
14
+ /**
15
+ * --alpha() — Color Transparency
16
+ *
17
+ * Creates a semi-transparent version of any color.
18
+ * Works exactly like CSS opacity property:
19
+ * - 0 = fully transparent (invisible)
20
+ * - 1 = fully opaque (solid)
21
+ * - 0.5 = 50% opaque (50% transparent)
22
+ *
23
+ * @param {color} --color - The base color
24
+ * @param {number} --opacity - Opacity level (0-1), default: 0.5
25
+ * Higher values = more opaque, lower values = more transparent
26
+ * @returns {color} - Color with applied opacity
27
+ *
28
+ * @example
29
+ * .overlay {
30
+ * background: --alpha(#6366f1, 0.2); // 20% opaque, 80% transparent
31
+ * }
32
+ * .backdrop {
33
+ * background: --alpha(var(--brand), 0.85); // 85% opaque, 15% transparent
34
+ * }
35
+ */
36
+
37
+ @function --alpha(--color <color>, --opacity <number>: 0.5) returns <color> {
38
+ result: oklch(from var(--color) l c h / var(--opacity));
39
+ }
40
+
41
+ /**
42
+ * --lighten() — Color Luminance Increase
43
+ *
44
+ * Adjust a color's lightness upward.
45
+ * Uses OKLCH lightness channel (0-1 range).
46
+ *
47
+ * @param {color} --color - The base color
48
+ * @param {number} --amount - Amount to lighten (0-1, e.g., 0.1 = 10%, 0.2 = 20%), default: 0.1
49
+ * @returns {color} - Lightened color
50
+ *
51
+ * @example
52
+ * .btn:hover {
53
+ * background: --lighten(var(--btn-bg), 0.15);
54
+ * }
55
+ */
56
+
57
+ @function --lighten(--color <color>, --amount <number>: 0.1) returns <color> {
58
+ result: oklch(from var(--color) calc(l + var(--amount)) c h);
59
+ }
60
+
61
+ /**
62
+ * --darken() — Color Luminance Decrease
63
+ *
64
+ * Adjust a color's lightness downward.
65
+ * Uses OKLCH lightness channel (0-1 range).
66
+ *
67
+ * @param {color} --color - The base color
68
+ * @param {number} --amount - Amount to darken (0-1, e.g., 0.1 = 10%, 0.2 = 20%), default: 0.1
69
+ * @returns {color} - Darkened color
70
+ *
71
+ * @example
72
+ * .btn:active {
73
+ * background: --darken(var(--btn-bg), 0.1);
74
+ * }
75
+ */
76
+
77
+ @function --darken(--color <color>, --amount <number>: 0.1) returns <color> {
78
+ result: oklch(from var(--color) calc(l - var(--amount)) c h);
79
+ }
80
+
81
+ /**
82
+ * --saturate() — Color Saturation Increase
83
+ *
84
+ * Adjust a color's chroma (saturation) intensity upward.
85
+ *
86
+ * @param {color} --color - The base color
87
+ * @param {number} --amount - Percentage as decimal (e.g., 0.4 = 40%), default: 0.2
88
+ * @returns {color} - More saturated color
89
+ *
90
+ * @example
91
+ * .vibrant {
92
+ * background: --saturate(var(--primary), 0.4);
93
+ * }
94
+ */
95
+
96
+ @function --saturate(
97
+ --color <color>,
98
+ --amount <number>: 0.2
99
+ ) returns <color> {
100
+ result: oklch(from var(--color) l calc(c + c * var(--amount)) h);
101
+ }
102
+
103
+ /**
104
+ * --desaturate() — Color Saturation Decrease
105
+ *
106
+ * Adjust a color's chroma (saturation) intensity downward.
107
+ *
108
+ * @param {color} --color - The base color
109
+ * @param {number} --amount - Percentage as decimal (e.g., 0.5 = 50%), default: 0.2
110
+ * @returns {color} - Less saturated (more muted) color
111
+ *
112
+ * @example
113
+ * .muted {
114
+ * color: --desaturate(var(--text), 0.5);
115
+ * }
116
+ */
117
+
118
+ @function --desaturate(
119
+ --color <color>,
120
+ --amount <number>: 0.2
121
+ ) returns <color> {
122
+ result: oklch(from var(--color) l calc(c - c * var(--amount)) h);
123
+ }
124
+
125
+ /**
126
+ * --mix() — Color Mixing
127
+ *
128
+ * Blend two colors by a given percentage.
129
+ *
130
+ * @param {color} --color1 - First color
131
+ * @param {color} --color2 - Second color
132
+ * @param {percentage} --weight - Weight of first color, default: 50%
133
+ * @returns {color} - Mixed color
134
+ *
135
+ * @example
136
+ * .gradient-mid {
137
+ * background: --mix(var(--start), var(--end), 50%);
138
+ * }
139
+ * .tinted {
140
+ * background: --mix(white, var(--brand), 90%);
141
+ * }
142
+ */
143
+
144
+ @function --mix(
145
+ --color1 <color>,
146
+ --color2 <color>,
147
+ --weight <percentage>: 50%
148
+ ) returns <color> {
149
+ result: color-mix(in oklch, var(--color1) var(--weight), var(--color2));
150
+ }
151
+
152
+ /**
153
+ * --contrast-text() — Auto Contrast Text Color
154
+ *
155
+ * Returns black or white depending on background luminance for optimal readability.
156
+ * Uses a mathematical approach with OKLCH lightness.
157
+ *
158
+ * @param {color} --bg - Background color
159
+ * @returns {color} - Black or white based on luminance
160
+ *
161
+ * @example
162
+ * .badge {
163
+ * background: var(--badge-color);
164
+ * color: --contrast-text(var(--badge-color));
165
+ * }
166
+ */
167
+
168
+ @function --contrast-text(--bg <color>) returns <color> {
169
+ result: oklch(from var(--bg) calc((0.6 - l) * infinity) 0 0);
170
+ }
171
+
172
+ /**
173
+ * --grayscale() — Grayscale Conversion
174
+ *
175
+ * Convert any color to grayscale by removing chroma (saturation).
176
+ * Uses OKLCH color space, setting chroma to 0 while preserving lightness.
177
+ *
178
+ * @param {color} --color - The base color
179
+ * @returns {color} - Grayscale version of the color
180
+ *
181
+ * @example
182
+ * .monochrome {
183
+ * background: --grayscale(#6366f1); // Blue becomes gray
184
+ * }
185
+ * .desaturated-image {
186
+ * filter: --grayscale(var(--image-color));
187
+ * }
188
+ */
189
+
190
+ @function --grayscale(--color <color>) returns <color> {
191
+ result: oklch(from var(--color) l 0 h);
192
+ }
193
+
194
+ /**
195
+ * --invert() — Color Inversion
196
+ *
197
+ * Invert a color by flipping its lightness in OKLCH space.
198
+ * Dark colors become light, light colors become dark.
199
+ *
200
+ * @param {color} --color - The base color
201
+ * @returns {color} - Inverted color
202
+ *
203
+ * @example
204
+ * .inverted {
205
+ * background: --invert(#ffffff); // White becomes black
206
+ * }
207
+ * .dark-mode-text {
208
+ * color: --invert(var(--light-text));
209
+ * }
210
+ */
211
+
212
+ @function --invert(--color <color>) returns <color> {
213
+ result: oklch(from var(--color) calc(1 - l) c h);
214
+ }
215
+
216
+ /**
217
+ * --complement() — Complementary Color
218
+ *
219
+ * Get the complementary color (opposite on color wheel) by rotating hue 180 degrees.
220
+ * Uses OKLCH color space for accurate hue rotation.
221
+ * Note: Achromatic colors (grays) have no hue, so their complement is unchanged.
222
+ *
223
+ * @param {color} --color - The base color
224
+ * @returns {color} - Complementary color
225
+ *
226
+ * @example
227
+ * .accent {
228
+ * background: --complement(#6366f1); // Blue's complement (orange)
229
+ * }
230
+ * .contrast-border {
231
+ * border-color: --complement(var(--primary));
232
+ * }
233
+ */
234
+
235
+ @function --complement(--color <color>) returns <color> {
236
+ result: oklch(from var(--color) l c calc(h + 180));
237
+ }
238
+
239
+ /* Spacing & Sizing */
240
+
241
+ /**
242
+ * --space() — Spacing Scale
243
+ *
244
+ * Returns spacing based on a consistent multiplier scale.
245
+ * The base unit can be customized via the --space-base CSS custom property.
246
+ *
247
+ * @param {number} --multiplier - Scale multiplier
248
+ * @param {length} --base - Base spacing unit, default: var(--space-base, 0.25rem)
249
+ * @returns {length} - Calculated spacing
250
+ *
251
+ * @example
252
+ * :root {
253
+ * --space-base: 0.25rem;
254
+ * }
255
+ *
256
+ * .card {
257
+ * padding: --space(4);
258
+ * margin-bottom: --space(6);
259
+ * gap: --space(2);
260
+ * }
261
+ *
262
+ * .special {
263
+ * padding: --space(4, 0.5rem);
264
+ * }
265
+ */
266
+
267
+ @function --space(--multiplier <number>, --base <length>: var(--space-base, 0.25rem)) returns <length> {
268
+ result: calc(var(--base) * var(--multiplier));
269
+ }
270
+
271
+ /**
272
+ * --fluid() — Fluid Viewport-Based Sizing
273
+ *
274
+ * Generates a clamp() value that scales smoothly between viewport widths.
275
+ * Use this for global, viewport-dependent responsive sizing.
276
+ *
277
+ * @param {length} --min - Minimum size
278
+ * @param {length} --max - Maximum size
279
+ * @param {length} --min-vw - Minimum viewport width, default: 375px
280
+ * @param {length} --max-vw - Maximum viewport width, default: 1440px
281
+ * @returns {length} - Fluid clamp value based on viewport width
282
+ *
283
+ * @example
284
+ * h1 {
285
+ * font-size: --fluid(1.5rem, 4rem);
286
+ * }
287
+ * .container {
288
+ * padding: --fluid(1rem, 3rem, 320px, 1200px);
289
+ * }
290
+ *
291
+ * @see --fluid-container() for container-based fluid sizing
292
+ */
293
+
294
+ @function --fluid(
295
+ --min <length>,
296
+ --max <length>,
297
+ --min-vw <length>: 375px,
298
+ --max-vw <length>: 1440px
299
+ ) returns <length> {
300
+ --slope: calc((var(--max) - var(--min)) / (var(--max-vw) - var(--min-vw)));
301
+ --intercept: calc(var(--min) - var(--slope) * var(--min-vw));
302
+ result: clamp(var(--min), calc(var(--intercept) + var(--slope) * 100vw), var(--max));
303
+ }
304
+
305
+ /**
306
+ * --fluid-container() — Fluid Container-Based Sizing
307
+ *
308
+ * Generates a clamp() value that scales smoothly based on container width.
309
+ * Requires the parent element to have container-type: inline-size or container: name / inline-size.
310
+ * Perfect for component-level responsive design independent of viewport size.
311
+ *
312
+ * @param {length} --min - Minimum size
313
+ * @param {length} --max - Maximum size
314
+ * @param {length} --min-cqw - Minimum container width, default: 20ch
315
+ * @param {length} --max-cqw - Maximum container width, default: 65ch
316
+ * @returns {length} - Fluid clamp value based on container width
317
+ *
318
+ * @example
319
+ * .card {
320
+ * container-type: inline-size;
321
+ * }
322
+ *
323
+ * .card h2 {
324
+ * font-size: --fluid-container(1rem, 2rem);
325
+ * }
326
+ *
327
+ * .card p {
328
+ * font-size: --fluid-container(0.875rem, 1.125rem, 30ch, 60ch);
329
+ * }
330
+ */
331
+
332
+ @function --fluid-container(
333
+ --min <length>,
334
+ --max <length>,
335
+ --min-cqw <length>: 20ch,
336
+ --max-cqw <length>: 65ch
337
+ ) returns <length> {
338
+ --slope: calc((var(--max) - var(--min)) / (var(--max-cqw) - var(--min-cqw)));
339
+ --intercept: calc(var(--min) - var(--slope) * var(--min-cqw));
340
+ result: clamp(var(--min), calc(var(--intercept) + var(--slope) * 100cqw), var(--max));
341
+ }
342
+
343
+ /**
344
+ * --ratio-height() — Aspect Ratio Height
345
+ *
346
+ * Calculate height from width and aspect ratio (useful for images/containers).
347
+ *
348
+ * @param {length} --width - Element width
349
+ * @param {number} --ratio-w - Ratio width part, default: 16
350
+ * @param {number} --ratio-h - Ratio height part, default: 9
351
+ * @returns {length} - Calculated height
352
+ *
353
+ * @example
354
+ * .video-thumb {
355
+ * width: 320px;
356
+ * height: --ratio-height(320px, 16, 9);
357
+ * }
358
+ * .square-avatar {
359
+ * width: 48px;
360
+ * height: --ratio-height(48px, 1, 1);
361
+ * }
362
+ */
363
+
364
+ @function --ratio-height(
365
+ --width <length>,
366
+ --ratio-w <number>: 16,
367
+ --ratio-h <number>: 9
368
+ ) returns <length> {
369
+ result: calc(var(--width) * var(--ratio-h) / var(--ratio-w));
370
+ }
371
+
372
+ /* Visual Effects */
373
+
374
+ /**
375
+ * --shadow() — Elevation Shadows
376
+ *
377
+ * Generates layered box-shadows based on elevation level (1-5).
378
+ *
379
+ * @param {number} --level - Elevation level (1-5)
380
+ * @param {color} --color - Shadow color, default: oklch(0% 0 0 / 0.1)
381
+ * @returns {string} - Layered box-shadow value (comma-separated shadow list)
382
+ *
383
+ * @example
384
+ * .card {
385
+ * box-shadow: --shadow(2);
386
+ * }
387
+ * .modal {
388
+ * box-shadow: --shadow(5, oklch(0% 0 0 / 0.25));
389
+ * }
390
+ */
391
+
392
+ @function --shadow(
393
+ --level <number>,
394
+ --color <color>: oklch(0% 0 0 / 0.1)
395
+ ) {
396
+ --y: calc(var(--level) * 2px);
397
+ --blur: calc(var(--level) * 4px);
398
+ --spread: calc(var(--level) * -1px);
399
+ result:
400
+ 0 var(--y) var(--blur) var(--spread) var(--color),
401
+ 0 calc(var(--y) * 0.5) calc(var(--blur) * 0.5) calc(var(--spread) * 0.5) var(--color);
402
+ }
403
+
404
+ /**
405
+ * --diagonal-lines() — Diagonal Line Pattern Background
406
+ *
407
+ * Generate a repeating diagonal line pattern with configurable angle, colors, and spacing.
408
+ * Returns a complete repeating-linear-gradient that can be used directly as a background value.
409
+ *
410
+ * @param {angle} --angle - Angle of the lines, default: -45deg
411
+ * @param {color} --line-color - Color of the lines, default: currentColor
412
+ * @param {length} --line-width - Width of each line, default: 1px
413
+ * @param {length} --gap-width - Width of the gap between lines, default: 4px
414
+ * @param {color} --gap-color - Color of the gap (background), default: transparent
415
+ * @returns {image} - Complete repeating-linear-gradient value
416
+ *
417
+ * Related - https://stackoverflow.com/questions/33091401/background-image-linear-gradient-jagged-edged-result-needs-to-be-smooth-edged
418
+ *
419
+ * @example
420
+ * .bg-diagonal {
421
+ * background: --diagonal-lines();
422
+ * }
423
+ *
424
+ * @example
425
+ * .custom-stripes {
426
+ * background: --diagonal-lines(
427
+ * --angle: 45deg,
428
+ * --line-color: #3b82f6,
429
+ * --line-width: 2px,
430
+ * --gap-width: 8px
431
+ * );
432
+ * }
433
+ *
434
+ * @example
435
+ * .subtle-pattern {
436
+ * color: #94a3b8;
437
+ * background: --diagonal-lines(
438
+ * --line-color: currentColor,
439
+ * --line-width: 1px,
440
+ * --gap-width: 10px
441
+ * );
442
+ * }
443
+ */
444
+
445
+ @function --diagonal-lines(
446
+ --angle <angle>: -45deg,
447
+ --line-color <color>: currentColor,
448
+ --line-width <length>: 1px,
449
+ --gap-width <length>: 4px,
450
+ --gap-color <color>: transparent
451
+ ) returns <image> {
452
+ result: repeating-linear-gradient(
453
+ var(--angle),
454
+ var(--line-color) 0,
455
+ var(--line-color) calc(var(--line-width) - 1px),
456
+ var(--gap-color) calc(var(--line-width) + 1px),
457
+ var(--gap-color) calc(var(--line-width) + var(--gap-width))
458
+ );
459
+ }
460
+
461
+ /* Layout Utilities */
462
+
463
+ /**
464
+ * --z() — Z-Index Scale
465
+ *
466
+ * Named z-index layers to avoid magic numbers and conflicts.
467
+ *
468
+ * Layer reference:
469
+ * 1: dropdown, 2: sticky, 3: fixed,
470
+ * 4: drawer, 5: modal, 6: popover,
471
+ * 7: tooltip, 8: toast, 9: max
472
+ *
473
+ * @param {number} --layer - Layer number (1-9)
474
+ * @returns {integer} - Z-index value (layer × 100)
475
+ *
476
+ * @example
477
+ * .dropdown { z-index: --z(1); }
478
+ * .header { z-index: --z(3); }
479
+ * .modal { z-index: --z(5); }
480
+ * .tooltip { z-index: --z(7); }
481
+ */
482
+
483
+ @function --z(--layer <number>) returns <integer> {
484
+ result: calc(var(--layer) * 100);
485
+ }
486
+
487
+ /**
488
+ * --neg() — Negative Value
489
+ *
490
+ * Simple negation — useful in calc-heavy layouts.
491
+ *
492
+ * @param {length} --value - Value to negate
493
+ * @returns {length} - Negative value
494
+ *
495
+ * @example
496
+ * .pull-up {
497
+ * margin-top: --neg(var(--header-height));
498
+ * }
499
+ * .bleed {
500
+ * margin-inline: --neg(var(--container-padding));
501
+ * }
502
+ */
503
+
504
+ @function --neg(--value <length>) returns <length> {
505
+ result: calc(var(--value) * -1);
506
+ }
507
+
508
+ /* Math Utilities */
509
+
510
+ /**
511
+ * --lerp() — Linear Interpolation
512
+ *
513
+ * Interpolates between two values using a t parameter (0-1).
514
+ * When t=0, returns a; when t=1, returns b; when t=0.5, returns midpoint.
515
+ * Hides the complex calc expression: calc(a + (b - a) * t)
516
+ *
517
+ * @param {length-percentage} --a - Start value
518
+ * @param {length-percentage} --b - End value
519
+ * @param {number} --t - Interpolation factor (0-1), default: 0.5
520
+ * @returns {length-percentage} - Interpolated value
521
+ *
522
+ * @example
523
+ * .animated {
524
+ * width: --lerp(100px, 500px, 0.3); // 30% between 100px and 500px
525
+ * }
526
+ * .transition {
527
+ * width: --lerp(10%, 80%, 0.75); // 75% between 10% and 80%
528
+ * }
529
+ */
530
+
531
+ @function --lerp(
532
+ --a <length-percentage>,
533
+ --b <length-percentage>,
534
+ --t <number>: 0.5
535
+ ) returns <length-percentage> {
536
+ result: calc(var(--a) * (1 - var(--t)) + var(--b) * var(--t));
537
+ }
538
+
539
+ /**
540
+ * --circle-x() — Circle X Position
541
+ *
542
+ * Calculate X coordinate on a circle using radius and angle.
543
+ * Uses cosine: r * cos(deg)
544
+ *
545
+ * @param {length} --radius - Circle radius
546
+ * @param {angle} --angle - Angle in degrees, default: 0deg
547
+ * @returns {length} - X coordinate offset from center
548
+ *
549
+ * @example
550
+ * .orbit {
551
+ * left: calc(50% + --circle-x(100px, 45deg)); // 45° position
552
+ * }
553
+ */
554
+
555
+ @function --circle-x(
556
+ --radius <length>,
557
+ --angle <angle>: 0deg
558
+ ) returns <length> {
559
+ result: calc(var(--radius) * cos(var(--angle)));
560
+ }
561
+
562
+ /**
563
+ * --circle-y() — Circle Y Position
564
+ *
565
+ * Calculate Y coordinate on a circle using radius and angle.
566
+ * Uses sine: r * sin(deg)
567
+ *
568
+ * @param {length} --radius - Circle radius
569
+ * @param {angle} --angle - Angle in degrees, default: 0deg
570
+ * @returns {length} - Y coordinate offset from center
571
+ *
572
+ * @example
573
+ * .orbit {
574
+ * top: calc(50% + --circle-y(100px, 45deg)); // 45° position
575
+ * }
576
+ */
577
+
578
+ @function --circle-y(
579
+ --radius <length>,
580
+ --angle <angle>: 0deg
581
+ ) returns <length> {
582
+ result: calc(var(--radius) * sin(var(--angle)));
583
+ }
584
+
585
+ /**
586
+ * --modular() — Modular Scale (Typographic Scale)
587
+ *
588
+ * Calculate values using a modular scale (typographic ratio).
589
+ * Hides the pow calculation: base * pow(ratio, step)
590
+ *
591
+ * @param {number} --step - Scale step (can be negative for smaller values)
592
+ * @param {length|number} --base - Base value, default: 1rem
593
+ * @param {number} --ratio - Scale ratio, default: 1.25 (major third)
594
+ * @returns {length|number} - Scaled value
595
+ *
596
+ * @example
597
+ * :root {
598
+ * --type-base: 1rem;
599
+ * --type-ratio: 1.25;
600
+ * }
601
+ * h1 {
602
+ * font-size: --modular(4, var(--type-base), var(--type-ratio)); // Large scale
603
+ * }
604
+ * small {
605
+ * font-size: --modular(-1, var(--type-base), var(--type-ratio)); // Smaller
606
+ * }
607
+ */
608
+
609
+ @function --modular(
610
+ --step <number>,
611
+ --base <length|number>: 1rem,
612
+ --ratio <number>: 1.25
613
+ ) returns <length|number> {
614
+ result: calc(var(--base) * pow(var(--ratio), var(--step)));
615
+ }
616
+
617
+ /**
618
+ * --poly-angle() — Polygon Vertex Angle
619
+ *
620
+ * Calculate the angle for a vertex in a regular polygon.
621
+ * Useful for creating polygon shapes with CSS transforms.
622
+ * Formula: (360deg / sides * index) - 90deg
623
+ *
624
+ * @param {number} --sides - Number of sides in polygon
625
+ * @param {number} --index - Vertex index (0-based), default: 0
626
+ * @returns {angle} - Angle in degrees
627
+ *
628
+ * @example
629
+ * .hexagon-vertex-0 {
630
+ * transform: rotate(--poly-angle(6, 0)); // First vertex of hexagon
631
+ * }
632
+ * .pentagon-vertex-2 {
633
+ * transform: rotate(--poly-angle(5, 2)); // Third vertex of pentagon
634
+ * }
635
+ */
636
+
637
+ @function --poly-angle(
638
+ --sides <number>,
639
+ --index <number>: 0
640
+ ) returns <angle> {
641
+ result: calc(360deg / var(--sides) * var(--index) - 90deg);
642
+ }
643
+
644
+ /* Unit Conversion */
645
+
646
+ /**
647
+ * --to-rem() — Pixel to REM Conversion
648
+ *
649
+ * Convert a pixel value to rem units. Base pixel size defaults to --rem-base (or 16px).
650
+ *
651
+ * @param {number} --px - Pixel value (unitless)
652
+ * @param {number} --base - Base pixel size for conversion, defaults to var(--rem-base, 16)
653
+ * @returns {length} - REM value
654
+ *
655
+ * @example
656
+ * .legacy-compat {
657
+ * font-size: --to-rem(18);
658
+ * padding: --to-rem(24);
659
+ * }
660
+ *
661
+ * .custom-base {
662
+ * font-size: --to-rem(18, 10);
663
+ * }
664
+ *
665
+ * :root {
666
+ * --rem-base: 20;
667
+ * }
668
+ */
669
+
670
+ @function --to-rem(--px <number>, --base <number>: var(--rem-base, 16)) returns <length> {
671
+ result: calc(var(--px) * 1rem / var(--base));
672
+ }
673
+
674
+ /**
675
+ * --to-px() — REM to Pixel Conversion
676
+ *
677
+ * Convert a rem value to pixels (assumes 16px base).
678
+ *
679
+ * @param {number} --rem - REM value (unitless)
680
+ * @returns {length} - Pixel value
681
+ *
682
+ * @example
683
+ * .legacy-system {
684
+ * width: --to-px(10);
685
+ * height: --to-px(5);
686
+ * }
687
+ */
688
+
689
+ @function --to-px(--rem <number>) returns <length> {
690
+ result: calc(var(--rem) * 16 * 1px);
691
+ }
692
+
693
+ /* Logic Operations */
694
+
695
+ /**
696
+ * --not() — Boolean Negation
697
+ *
698
+ * Inverts a boolean value (switch variable). Works with values 0 and 1.
699
+ * Returns 1 if input is 0, returns 0 if input is 1.
700
+ *
701
+ * @param {number} --value - Switch variable (0 or 1)
702
+ * @returns {number} - Inverted value (0 or 1)
703
+ *
704
+ * @example
705
+ * .toggle {
706
+ * --is-active: 1;
707
+ * --is-inactive: --not(var(--is-active)); // 0
708
+ * opacity: --not(var(--is-hidden));
709
+ * }
710
+ *
711
+ * @see https://css-tricks.com/logical-operations-with-css-variables/
712
+ */
713
+
714
+ @function --not(--value <number>) returns <number> {
715
+ result: calc(1 - var(--value));
716
+ }
717
+
718
+ /**
719
+ * --and() — Logical AND Operation
720
+ *
721
+ * Returns 1 if both operands are 1, otherwise returns 0.
722
+ * Uses multiplication since any value multiplied by 0 equals 0.
723
+ *
724
+ * Truth table:
725
+ * - 0 AND 0 = 0
726
+ * - 0 AND 1 = 0
727
+ * - 1 AND 0 = 0
728
+ * - 1 AND 1 = 1
729
+ *
730
+ * @param {number} --a - First switch variable (0 or 1)
731
+ * @param {number} --b - Second switch variable (0 or 1)
732
+ * @returns {number} - Result (0 or 1)
733
+ *
734
+ * @example
735
+ * .visible-on-mobile-and-dark {
736
+ * --is-mobile: 1;
737
+ * --is-dark-mode: 1;
738
+ * display: --and(var(--is-mobile), var(--is-dark-mode));
739
+ * }
740
+ *
741
+ * @see https://css-tricks.com/logical-operations-with-css-variables/
742
+ */
743
+
744
+ @function --and(--a <number>, --b <number>) returns <number> {
745
+ result: calc(var(--a) * var(--b));
746
+ }
747
+
748
+ /**
749
+ * --or() — Logical OR Operation
750
+ *
751
+ * Returns 1 if at least one operand is 1, otherwise returns 0.
752
+ * Uses De Morgan's law: NOT (A OR B) = (NOT A) AND (NOT B)
753
+ *
754
+ * Truth table:
755
+ * - 0 OR 0 = 0
756
+ * - 0 OR 1 = 1
757
+ * - 1 OR 0 = 1
758
+ * - 1 OR 1 = 1
759
+ *
760
+ * @param {number} --a - First switch variable (0 or 1)
761
+ * @param {number} --b - Second switch variable (0 or 1)
762
+ * @returns {number} - Result (0 or 1)
763
+ *
764
+ * @example
765
+ * .show-on-mobile-or-tablet {
766
+ * --is-mobile: 0;
767
+ * --is-tablet: 1;
768
+ * display: --or(var(--is-mobile), var(--is-tablet));
769
+ * }
770
+ *
771
+ * @see https://css-tricks.com/logical-operations-with-css-variables/
772
+ */
773
+
774
+ @function --or(--a <number>, --b <number>) returns <number> {
775
+ result: calc(1 - (1 - var(--a)) * (1 - var(--b)));
776
+ }
777
+
778
+ /**
779
+ * --xor() — Exclusive OR Operation
780
+ *
781
+ * Returns 1 if exactly one operand is 1, otherwise returns 0.
782
+ * Uses subtraction squared to get absolute value: (a - b)²
783
+ *
784
+ * Truth table:
785
+ * - 0 XOR 0 = 0 (both same)
786
+ * - 0 XOR 1 = 1 (different)
787
+ * - 1 XOR 0 = 1 (different)
788
+ * - 1 XOR 1 = 0 (both same)
789
+ *
790
+ * @param {number} --a - First switch variable (0 or 1)
791
+ * @param {number} --b - Second switch variable (0 or 1)
792
+ * @returns {number} - Result (0 or 1)
793
+ *
794
+ * @example
795
+ * .highlight-when-different {
796
+ * --user-theme: 1;
797
+ * --system-theme: 0;
798
+ * opacity: --xor(var(--user-theme), var(--system-theme));
799
+ * }
800
+ *
801
+ * @see https://css-tricks.com/logical-operations-with-css-variables/
802
+ */
803
+
804
+ @function --xor(--a <number>, --b <number>) returns <number> {
805
+ result: calc((var(--a) - var(--b)) * (var(--a) - var(--b)));
806
+ }
807
+
808
+ /**
809
+ * --nand() — Logical NAND Operation (NOT AND)
810
+ *
811
+ * Returns 0 if both operands are 1, otherwise returns 1.
812
+ * Combination of NOT and AND operations.
813
+ *
814
+ * Truth table:
815
+ * - 0 NAND 0 = 1
816
+ * - 0 NAND 1 = 1
817
+ * - 1 NAND 0 = 1
818
+ * - 1 NAND 1 = 0
819
+ *
820
+ * @param {number} --a - First switch variable (0 or 1)
821
+ * @param {number} --b - Second switch variable (0 or 1)
822
+ * @returns {number} - Result (0 or 1)
823
+ *
824
+ * @example
825
+ * .hide-when-both-active {
826
+ * --feature-a: 1;
827
+ * --feature-b: 1;
828
+ * display: --nand(var(--feature-a), var(--feature-b));
829
+ * }
830
+ *
831
+ * @see https://css-tricks.com/logical-operations-with-css-variables/
832
+ */
833
+
834
+ @function --nand(--a <number>, --b <number>) returns <number> {
835
+ result: calc(1 - var(--a) * var(--b));
836
+ }
837
+
838
+ /**
839
+ * --nor() — Logical NOR Operation (NOT OR)
840
+ *
841
+ * Returns 1 if both operands are 0, otherwise returns 0.
842
+ * Combination of NOT and OR operations.
843
+ *
844
+ * Truth table:
845
+ * - 0 NOR 0 = 1
846
+ * - 0 NOR 1 = 0
847
+ * - 1 NOR 0 = 0
848
+ * - 1 NOR 1 = 0
849
+ *
850
+ * @param {number} --a - First switch variable (0 or 1)
851
+ * @param {number} --b - Second switch variable (0 or 1)
852
+ * @returns {number} - Result (0 or 1)
853
+ *
854
+ * @example
855
+ * .show-default-state {
856
+ * --has-custom-bg: 0;
857
+ * --has-custom-text: 0;
858
+ * opacity: --nor(var(--has-custom-bg), var(--has-custom-text));
859
+ * }
860
+ *
861
+ * @see https://css-tricks.com/logical-operations-with-css-variables/
862
+ */
863
+
864
+ @function --nor(--a <number>, --b <number>) returns <number> {
865
+ result: calc((1 - var(--a)) * (1 - var(--b)));
866
+ }
867
+
868
+ /**
869
+ * --xnor() — Exclusive NOR Operation (NOT XOR)
870
+ *
871
+ * Returns 1 if both operands are the same, otherwise returns 0.
872
+ * The opposite of XOR - checks for equality.
873
+ *
874
+ * Truth table:
875
+ * - 0 XNOR 0 = 1 (both same)
876
+ * - 0 XNOR 1 = 0 (different)
877
+ * - 1 XNOR 0 = 0 (different)
878
+ * - 1 XNOR 1 = 1 (both same)
879
+ *
880
+ * @param {number} --a - First switch variable (0 or 1)
881
+ * @param {number} --b - Second switch variable (0 or 1)
882
+ * @returns {number} - Result (0 or 1)
883
+ *
884
+ * @example
885
+ * .sync-indicator {
886
+ * --local-state: 1;
887
+ * --remote-state: 1;
888
+ * opacity: --xnor(var(--local-state), var(--remote-state));
889
+ * }
890
+ *
891
+ * @see https://css-tricks.com/logical-operations-with-css-variables/
892
+ */
893
+
894
+ @function --xnor(--a <number>, --b <number>) returns <number> {
895
+ result: calc(1 - (var(--a) - var(--b)) * (var(--a) - var(--b)));
896
+ }
897
+