@anyblades/blades 2.4.9 → 3.0.0-alpha.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.
Files changed (58) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. package/.prettierrc.json +6 -0
  4. package/README.md +112 -149
  5. package/context7.json +4 -0
  6. package/css/blades.css +436 -153
  7. package/css/blades.min.css +145 -0
  8. package/css/blades.standalone.css +197 -222
  9. package/css/blades.standalone.min.css +37 -0
  10. package/css/{blades.ninja.css → ninja.css} +7 -2
  11. package/css/ninja.min.css +5 -0
  12. package/css/pico.conditional.css +3189 -0
  13. package/css/pico.conditional.min.css +107 -0
  14. package/css/pico.css +3186 -0
  15. package/css/pico.min.css +105 -0
  16. package/css/postcss.config.js +23 -0
  17. package/index.html +63 -0
  18. package/package.json +27 -11
  19. package/src/blades.css +4 -3
  20. package/src/blades.standalone.css +2 -2
  21. package/src/{blades.ninja.css → ninja.css} +7 -2
  22. package/src/pico/_components.css +974 -0
  23. package/src/pico/_content.css +608 -0
  24. package/src/pico/_forms.css +747 -0
  25. package/src/pico/_index.css +12 -0
  26. package/src/pico/_layout.css +128 -0
  27. package/src/pico/_utilities.css +53 -0
  28. package/src/pico/_variables.css +671 -0
  29. package/src/pico.css +1 -0
  30. package/src/standalone/_core.css +32 -0
  31. package/src/{blades.standalone.theme.css → standalone/_theme.css} +2 -2
  32. package/src/{_utilities.css → standalone/_utilities.css} +1 -1
  33. package/src/standalone/_variables.css +20 -0
  34. package/src/{link-icon.css → standalone/components/_icon-helper.css} +0 -2
  35. package/src/{components → standalone/components}/_timeline.css +1 -1
  36. package/src/{content → standalone/content}/_code.css +6 -5
  37. package/src/{content/_typography.css → standalone/content/_list.css} +4 -42
  38. package/src/standalone/content/_typography.css +38 -0
  39. package/src/{float-label.core.css → standalone/forms/_float-label.core.css} +12 -4
  40. package/src/standalone/forms/_float-label.css +2 -0
  41. package/src/{breakout.css → standalone/layout/_breakout.css} +3 -6
  42. package/src/{_layout.css → standalone/layout/_columns.css} +2 -12
  43. package/src/standalone/layout/_scroller.css +10 -0
  44. package/_config.yml +0 -5
  45. package/blades.gemspec +0 -16
  46. package/css/blades.standalone.core.css +0 -712
  47. package/css/blades.standalone.theme.css +0 -143
  48. package/css/breakout.css +0 -125
  49. package/css/float-label.core.css +0 -56
  50. package/css/float-label.css +0 -96
  51. package/css/float-label.theme.css +0 -39
  52. package/css/link-icon.css +0 -59
  53. package/css/responsive-table.css +0 -57
  54. package/src/blades.standalone.core.css +0 -23
  55. package/src/float-label.css +0 -3
  56. /package/src/{responsive-table.css → standalone/content/_table-responsive.css} +0 -0
  57. /package/src/{content → standalone/content}/_table.css +0 -0
  58. /package/src/{float-label.theme.css → standalone/forms/_float-label.theme.css} +0 -0
@@ -1,74 +1,51 @@
1
+ /*!
2
+ * Blades CSS standalone kit v3 🥷
3
+ * Copyright 2026 (https://blades.ninja/css/standalone/)
4
+ * Licensed under MIT
5
+ */
1
6
  /*\3c !--section:code-->```css */
2
7
 
3
8
  /* Follows https://github.com/picocss/pico/blob/main/scss/_index.scss */
4
9
 
5
- /* Layout */
6
-
7
- /*\3c !--section:columns-->
8
-
9
- `.columns` automatically creates columns with at least 30 characters each:
10
-
11
- <article class="columns">
12
- <p>1</p><p>2</p><p>3</p><p>4</p><p>5</p><p>6</p>
13
- </article>
14
-
15
- The smaller the font size, the more columns will be created:
16
-
17
- <article class="columns" style="font-size: 65%">
18
- <p>1</p><p>2</p><p>3</p><p>4</p><p>5</p><p>6</p><p>7</p><p>8</p><p>9</p>
19
- </article>
20
-
21
- Useful for tables of contents and long lists.
22
-
23
- How it works:
24
- ```css */
25
-
26
- .columns {
27
- -moz-columns: 25ch auto;
28
- columns: 25ch auto; /* common container 65ch / 25ch => 2 columns max, with a buffer for list paddings */
29
- }
30
-
31
- /* Avoid breaking inside elements, such as nested lists */
32
-
33
- .columns > * {
34
- -moz-column-break-inside: avoid;
35
- break-inside: avoid;
36
- }
37
-
38
- /*```\3c !--section:jump-->
39
-
40
- `data-jump-to="top"` fixes element to the corner and adds extra top padding to make it easy to click:
41
- ```css */
42
-
43
- [data-jump-to="top"] {
44
- position: fixed;
45
- right: 0;
46
- bottom: 0;
47
- padding-top: 50vh;
10
+ :scope,
11
+ :host {
12
+ /**
13
+ * Design Tokens !!! NAMING NOT FINALIZED YET !!!
14
+ * `--in-pico: *` props below are for reference only (not to be used)
15
+ */
16
+ --blades-token-primary: var(--pico-primary, dodgerblue);
17
+ --in-pico: #01aaff;
18
+ --blades-token-secondary: var(--pico-secondary, lightslategray);
19
+ --in-pico: #969eaf;
20
+
21
+ /**
22
+ * Inherited Variables
23
+ */
24
+ --blades-timeline-border-color: color-mix(
25
+ in srgb,
26
+ var(--blades-token-secondary) 50%,
27
+ transparent
28
+ );
48
29
  }
49
30
 
31
+ /* Layout */
50
32
  /*
51
33
  \3c !--section:code-->
52
34
  ```css */
53
-
54
35
  .breakout,
55
36
  .breakout-all {
56
37
  max-width: calc(10% + 65ch + 10%);
57
38
  /* Prepare the container for breakout elements */
58
39
  padding-inline: 10%;
59
40
  }
60
-
61
41
  @media (width < 768px) {
62
-
63
42
  .breakout,
64
43
  .breakout-all {
65
44
  max-width: calc(1rem + 65ch + 1rem);
66
45
  padding-inline: 1rem
67
46
  }
68
47
  }
69
-
70
48
  /* Breakout direct children only */
71
-
72
49
  .breakout > *:is(
73
50
  table,
74
51
  pre,
@@ -94,17 +71,14 @@ How it works:
94
71
  margin-left: 50%;
95
72
  transform: translateX(-50%);
96
73
  }
97
-
98
74
  /* Respect img/picture min-width */
99
-
100
75
  .breakout > *:is(img, picture):not(.does-not-exist), .breakout-all > *:is(img, picture):not(.does-not-exist) /* soft win specificity over .breakout-item above */ {
101
76
  min-width: auto;
102
77
  }
103
-
104
78
  /* Max out the width of the element */
105
-
106
79
  .breakout > *.breakout-item-max, .breakout-all > *.breakout-item-max {
107
- width: min(125%, 100dvw) !important; /* !important is for cases like figure.breakout-item-max @TODO */
80
+ width: min(125%, 100dvw) !important;
81
+ /* !important is for cases like figure.breakout-item-max @TODO */
108
82
  }
109
83
 
110
84
  .breakout-all > *:is(h2, h3, h4, h5, h6, hr):not([class]) {
@@ -147,15 +121,10 @@ How it works:
147
121
  height: 100%;
148
122
  transform: translateX(-50%);
149
123
  }
150
-
151
124
  /*```
152
- \3c !--section:summary-->
153
-
154
- The `.breakout` layout allows images, tables, and other figures to automatically extend or bleed beyond their parent container’s width.
155
-
156
125
  \3c !--section:docs-->
157
126
 
158
- ### Demo \3c !-- inside parent .breakout -->
127
+ The `.breakout` layout allows images, tables, and other figures to automatically extend or bleed beyond their parent container’s width.
159
128
 
160
129
  Break out a wide image from the text flow:
161
130
 
@@ -189,10 +158,48 @@ Or anything else:
189
158
  <hr>
190
159
 
191
160
  \3c !--section--> */
161
+ /*\3c !--section:docs-->
162
+
163
+ `.columns` automatically creates columns with at least 30 characters each:
164
+
165
+ <article class="columns">
166
+ <p>1</p><p>2</p><p>3</p><p>4</p><p>5</p><p>6</p>
167
+ </article>
168
+
169
+ The smaller the font size, the more columns will be created:
170
+
171
+ <article class="columns" style="font-size: 65%">
172
+ <p>1</p><p>2</p><p>3</p><p>4</p><p>5</p><p>6</p><p>7</p><p>8</p><p>9</p>
173
+ </article>
174
+
175
+ Useful for tables of contents and long lists.
176
+
177
+ \3c !--section:code-->
178
+ ```css */
179
+ .columns {
180
+ -moz-columns: 25ch auto;
181
+ columns: 25ch auto; /* common container 65ch / 25ch => 2 columns max, with a buffer for list paddings */
182
+ }
183
+ /* Avoid breaking inside elements, such as nested lists */
184
+ .columns > * {
185
+ -moz-column-break-inside: avoid;
186
+ break-inside: avoid;
187
+ }
188
+ /*```\3c !--section:docs-->
189
+
190
+ `<a href="#top">` is automatically fixed to the bottom-right corner with extra top padding for easier clicking:
191
+ ```css */
192
+ [href="#top"] {
193
+ position: fixed;
194
+ right: 0;
195
+ bottom: 0;
196
+ padding-top: 50vh;
197
+ }
192
198
 
193
199
  /* Content */
200
+ /*\3c !--section:docs-->
194
201
 
195
- /*\3c !--section:h-anchor-->
202
+ ## Heading anchors
196
203
 
197
204
  Links with `href="#..."` inside headings are automatically displayed as anchors:
198
205
 
@@ -202,7 +209,6 @@ Links with `href="#..."` inside headings are automatically displayed as anchors:
202
209
 
203
210
  How it works:
204
211
  ```css */
205
-
206
212
  h1,
207
213
  h2,
208
214
  h3,
@@ -211,7 +217,6 @@ h5,
211
217
  h6 {
212
218
  position: relative;
213
219
  }
214
-
215
220
  h1 a[aria-hidden="true"], h2 a[aria-hidden="true"], h3 a[aria-hidden="true"], h4 a[aria-hidden="true"], h5 a[aria-hidden="true"], h6 a[aria-hidden="true"] {
216
221
  visibility: hidden;
217
222
  position: absolute;
@@ -222,18 +227,14 @@ h1 a[aria-hidden="true"], h2 a[aria-hidden="true"], h3 a[aria-hidden="true"], h4
222
227
  color: silver;
223
228
  text-decoration: none;
224
229
  }
225
-
226
230
  @media /* to avoid double-tap on touch devices */ (hover: hover) {
227
231
  h1:hover a[aria-hidden="true"], h2:hover a[aria-hidden="true"], h3:hover a[aria-hidden="true"], h4:hover a[aria-hidden="true"], h5:hover a[aria-hidden="true"], h6:hover a[aria-hidden="true"] {
228
232
  visibility: visible;
229
233
  }
230
234
  }
235
+ /*\3c !--section:docs-->
231
236
 
232
- /*```
233
-
234
- **PRO** example of automatic anchors for `11ty`+`markdown-it-anchor`: https://github.com/anyblades/eleventy-blades/blob/main/src/eleventy.config.js
235
-
236
- \3c !--section:list-->
237
+ ## Custom markers
237
238
 
238
239
  Customize markers using inline `style="--list-marker:..."` on `<ul>/<ol>` or even individual `<li>`:
239
240
 
@@ -252,26 +253,21 @@ Customize markers using inline `style="--list-marker:..."` on `<ul>/<ol>` or eve
252
253
 
253
254
  How it works:
254
255
  ```css */
255
-
256
256
  ul[style*="--list-marker:"], ol[style*="--list-marker:"] {
257
257
  list-style-type: var(--list-marker);
258
258
  }
259
-
260
259
  ul[style*="--list-marker:"] > li, ol[style*="--list-marker:"] > li {
261
260
  list-style-type: inherit;
262
261
  }
263
-
264
262
  ul li[style*="--list-marker:"], ol li[style*="--list-marker:"] {
265
263
  list-style-type: var(--list-marker);
266
264
  }
267
-
268
265
  ul li[data-marker]::marker, ol li[data-marker]::marker {
269
266
  content: attr(data-marker) " "; /* ⚠️ Chrome and Firefox only */
270
267
  }
271
-
272
268
  /*```
273
269
 
274
- ### Markerless
270
+ ## Markerless
275
271
 
276
272
  `.markerless` removes markers and reduces padding:
277
273
 
@@ -286,18 +282,15 @@ ul li[data-marker]::marker, ol li[data-marker]::marker {
286
282
 
287
283
  How it works:
288
284
  ```css */
289
-
290
285
  ul.markerless, ol.markerless {
291
286
  padding-inline-start: 1.25rem;
292
287
  }
293
-
294
288
  ul.markerless li, ol.markerless li {
295
289
  list-style: none;
296
290
  }
297
-
298
291
  /*```
299
292
 
300
- ### Unlist
293
+ ## Unlist
301
294
 
302
295
  `.unlist` removes list styling at all:
303
296
 
@@ -314,7 +307,6 @@ ul.markerless li, ol.markerless li {
314
307
 
315
308
  How it works:
316
309
  ```css */
317
-
318
310
  ul.unlist,
319
311
  ul.unlist-all,
320
312
  .unlist-all ul,
@@ -323,80 +315,9 @@ ul.unlist,
323
315
  .unlist-all ol {
324
316
  padding-inline-start: 0;
325
317
  }
326
-
327
318
  ul.unlist > li, ul.unlist-all > li, .unlist-all ul > li, ol.unlist > li, ol.unlist-all > li, .unlist-all ol > li {
328
319
  list-style: none;
329
320
  }
330
-
331
- /* Extends https://github.com/picocss/pico/blob/main/scss/content/_link.scss
332
- \3c !--section:code-->
333
- ```css */
334
-
335
- /* Use inline flex only if link contains an icon */
336
-
337
- a:not(.does-not-exist):has(> i) {
338
- display: inline-flex;
339
- overflow-y: clip; /* to work in pair with text-underline-offset in Safari */
340
- gap: 0.375ch; /* =3/8 */
341
- text-wrap: balance;
342
- }
343
-
344
- a:not(.does-not-exist) > i {
345
- float: left; /* ✅ Chrome ❌ Safari */
346
- font-style: normal;
347
- text-underline-offset: -2em; /* ❌ Chrome ✅ Safari - to clip it with overflow-y */
348
- }
349
-
350
- /* Favicons */
351
-
352
- a:not(.does-not-exist) > i > img {
353
- display: inline-block; /* for Tailwind CSS Typography */
354
- max-width: none; /* to keep ratio safe */
355
- height: 1.25em;
356
- margin-block-start: calc(-0.25em / 2);
357
- }
358
-
359
- a:not(.does-not-exist) > i[class^="fa-"],
360
- a:not(.does-not-exist) > i[class*=" fa-"] {
361
- line-height: inherit;
362
- --fa-width: auto;
363
- }
364
-
365
- /* Font Awesome */
366
-
367
- a:not(.does-not-exist) > i.fa-lg {
368
- line-height: normal;
369
- }
370
-
371
- /*```
372
- \3c !--section:summary-->
373
-
374
- Use *Bl*ades `<i>`-helper to wrap emojis, favicons, or simply drop Font Awesome icons inside links.
375
- It automatically handles sizing and alignment while preventing underline on icons.
376
-
377
- \3c !--section:docs-->
378
-
379
- <article class="breakout-item">
380
-
381
- | Use `<i>`-helper with | Clean HTML without `<span>ning` |
382
- | ------------------------------------------------------------------------------------- | ------------------------------------ |
383
- | [<i>🥷</i> Emojis](#) | `<a><i>🥷</i> Text</a>` |
384
- | [<i>![](https://www.google.com/s2/favicons?domain=any.digital&sz=64)</i> Favicons](#) | `<a><i><img src="..."></i> Text</a>` |
385
- | [<i class="fa-brands fa-github fa-lg"></i> Font Awesome icons](#) | `<a><i class="fa-..."></i> Text</a>` |
386
- </article>
387
-
388
- Or even for:
389
-
390
- <article>
391
- <a href="#" style="margin: 0 0 0 1rem"><i>🥷</i> very-very-very-very-very-<br>long-multiline-links</a>
392
- </article>
393
-
394
- How we made it: https://codepen.io/editor/anydigital/pen/019d2b94-5616-75dc-a23e-e111869d5237
395
-
396
- **PRO** example of automatic favicons for `11ty`: https://blades.ninja/build-awesome-11ty/processors/#auto-link-favicons
397
-
398
- \3c !--section--> */
399
-
400
321
  /*\3c !--section:docs-->
401
322
 
402
323
  ## Column expanders
@@ -418,22 +339,18 @@ Living examples of big tables with `<hr>`-expanders: \3c !--A-Z-->
418
339
 
419
340
  How it works:
420
341
  ```css */
421
-
422
342
  th hr {
423
343
  visibility: hidden;
424
344
  width: 12ch; /* min ~65/12 = ~5 cols */
425
345
  height: 0;
426
346
  margin: 0;
427
347
  }
428
-
429
348
  th hr.lg {
430
349
  width: 18ch;
431
350
  }
432
-
433
351
  th hr.x2 {
434
352
  width: 24ch;
435
353
  }
436
-
437
354
  /*```
438
355
 
439
356
  ## Borderless table
@@ -450,16 +367,13 @@ th hr.x2 {
450
367
  </article>
451
368
 
452
369
  \3c !--section-->*/
453
-
454
370
  table.borderless th,
455
371
  table.borderless td {
456
372
  border: none;
457
373
  }
458
-
459
374
  /*
460
375
  \3c !--section:code-->
461
376
  ```css */
462
-
463
377
  table.responsive,
464
378
  .breakout > table:not(.does-not-exist),
465
379
  .breakout-all > table:not(.does-not-exist) {
@@ -479,7 +393,6 @@ table.responsive,
479
393
  transform: translateX(-50%);
480
394
  -webkit-overflow-scrolling: touch; /* Smooth scroll for iOS */
481
395
  }
482
-
483
396
  table.responsive th,
484
397
  table.responsive td,
485
398
  .breakout > table:not(.does-not-exist) th,
@@ -488,7 +401,6 @@ table.responsive th,
488
401
  .breakout-all > table:not(.does-not-exist) td {
489
402
  padding-inline-start: 0;
490
403
  }
491
-
492
404
  /*```
493
405
  `table:not(.does-not-exist)` trick (inspired by postcss) is used here to increase specificity against selectors like `&:is(table, .table)`
494
406
 
@@ -516,23 +428,21 @@ Living examples: \3c !--A-Z-->
516
428
  - https://blades.ninja/ssg/
517
429
 
518
430
  \3c !--section--> */
519
-
520
431
  /*\3c !--section:docs-->
521
432
 
522
433
  The `<pre><code>` blocks are Prism-compatible and support captions via `data-caption="..."` attribute:
523
434
 
524
- ```treeview {data-caption="Blades CSS:"}
525
- ./css/
526
- ├── blades.core.css # reusable class-light utilities, unthemed
527
- ├── blades.theme.css # minimal opinionated theme
528
- └── blades.css # above two together
435
+ ```treeview {data-caption="Blades CSS ships:"}
436
+ /css/
437
+ ├── blades.css (Pico CSS successor)
438
+ ├── blades.standalone.css (framework-agnostic)
439
+ ├── ninja.css (experimental add-ons)
440
+ └── pico.css (underlying fork)
529
441
  ```
530
442
 
531
443
  \3c !--section:code-->
532
444
  ```css */
533
-
534
445
  @media (width < 768px) {
535
-
536
446
  pre {
537
447
  border-radius: 0
538
448
  }
@@ -549,28 +459,43 @@ code[data-caption]::before {
549
459
  }
550
460
 
551
461
  /* Forms */
552
-
553
- /* Sources moved here from https://github.com/anyblades/float-label-css for easier maintenance
554
- \3c !--section:docs-->
462
+ /*!
463
+ * Float Label CSS v2 by Blades CSS 🥷🏷️
464
+ * Copyright 2016–2026 (https://blades.ninja/css/float-label/)
465
+ * Licensed under MIT
466
+ */
467
+ /*\3c !--section:docs-->
555
468
 
556
469
  First, we target either:
557
470
  1. `<label>` which `:has` inner form inputs (classless approach)
558
471
  2. or explicit `.has-float-label` class (explicit approach)
559
472
 
560
473
  ```css */
561
-
562
- label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select),
474
+ label:has(> span):has(
475
+ input:not([type="checkbox"], [type="radio"], [type="range"]),
476
+ textarea,
477
+ select
478
+ ),
563
479
  .has-float-label {
564
480
  display: block;
565
481
  position: relative;
482
+ /*```
483
+ Finally, we detect if placeholder is shown, but not in focus. That means we can safely hide it, and enlarge the float label instead:
484
+ ```css */
566
485
  }
567
-
568
486
  /*```
569
487
  Then, we define the default/fallback state (when the float label should be minimized):
570
488
  ```css */
571
-
572
- label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) > span,
573
- label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) label,
489
+ label:has(> span):has(
490
+ input:not([type="checkbox"], [type="radio"], [type="range"]),
491
+ textarea,
492
+ select
493
+ ) > span,
494
+ label:has(> span):has(
495
+ input:not([type="checkbox"], [type="radio"], [type="range"]),
496
+ textarea,
497
+ select
498
+ ) label,
574
499
  .has-float-label > span,
575
500
  .has-float-label label {
576
501
  position: absolute;
@@ -579,46 +504,120 @@ label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"
579
504
  font-size: 75%;
580
505
  cursor: text;
581
506
  }
582
-
583
- /*```
584
- Finally, we detect if placeholder is shown, but not in focus. That means we can safely hide it, and enlarge the float label instead:
585
- ```css */
586
-
587
- label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) *:placeholder-shown:not(:focus)::-moz-placeholder, .has-float-label *:placeholder-shown:not(:focus)::-moz-placeholder {
507
+ label:has(> span):has(
508
+ input:not([type="checkbox"], [type="radio"], [type="range"]),
509
+ textarea,
510
+ select
511
+ ) *:placeholder-shown:not(:focus)::-moz-placeholder, .has-float-label *:placeholder-shown:not(:focus)::-moz-placeholder {
588
512
  opacity: 0;
589
513
  }
590
-
591
- label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) *:-moz-placeholder:not(:focus)::placeholder, .has-float-label *:-moz-placeholder:not(:focus)::placeholder {
514
+ label:has(> span):has(
515
+ input:not([type="checkbox"], [type="radio"], [type="range"]),
516
+ textarea,
517
+ select
518
+ ) *:-moz-placeholder:not(:focus)::placeholder, .has-float-label *:-moz-placeholder:not(:focus)::placeholder {
592
519
  opacity: 0;
593
520
  }
594
-
595
- label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) *:placeholder-shown:not(:focus)::placeholder, .has-float-label *:placeholder-shown:not(:focus)::placeholder {
521
+ label:has(> span):has(
522
+ input:not([type="checkbox"], [type="radio"], [type="range"]),
523
+ textarea,
524
+ select
525
+ ) *:placeholder-shown:not(:focus)::placeholder, .has-float-label *:placeholder-shown:not(:focus)::placeholder {
596
526
  opacity: 0;
597
527
  }
598
-
599
- label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select):has(*:-moz-placeholder:not(:focus)) > span, label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select):has(*:-moz-placeholder:not(:focus)) label, .has-float-label:has(*:-moz-placeholder:not(:focus)) > span, .has-float-label:has(*:-moz-placeholder:not(:focus)) label {
528
+ label:has(> span):has(
529
+ input:not([type="checkbox"], [type="radio"], [type="range"]),
530
+ textarea,
531
+ select
532
+ ):has(*:-moz-placeholder:not(:focus)) > span, label:has(> span):has(
533
+ input:not([type="checkbox"], [type="radio"], [type="range"]),
534
+ textarea,
535
+ select
536
+ ):has(*:-moz-placeholder:not(:focus)) label, .has-float-label:has(*:-moz-placeholder:not(:focus)) > span, .has-float-label:has(*:-moz-placeholder:not(:focus)) label {
600
537
  font-size: inherit;
601
538
  opacity: 50%;
602
539
  }
603
-
604
- label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select):has(*:placeholder-shown:not(:focus)) > span,
605
- label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select):has(*:placeholder-shown:not(:focus)) label,
540
+ label:has(> span):has(
541
+ input:not([type="checkbox"], [type="radio"], [type="range"]),
542
+ textarea,
543
+ select
544
+ ):has(*:placeholder-shown:not(:focus)) > span,
545
+ label:has(> span):has(
546
+ input:not([type="checkbox"], [type="radio"], [type="range"]),
547
+ textarea,
548
+ select
549
+ ):has(*:placeholder-shown:not(:focus)) label,
606
550
  .has-float-label:has(*:placeholder-shown:not(:focus)) > span,
607
551
  .has-float-label:has(*:placeholder-shown:not(:focus)) label {
608
552
  font-size: inherit;
609
553
  opacity: 50%;
610
554
  }
611
-
612
555
  /*```
613
556
 
614
557
  The `:has(*:placeholder-shown:not(:focus))` trick allows this input state information to *propagate* to the parent level. This enables modern CSS to target inner float label (`<span>` or `<label>`) regardless of its position relative to the input field.
615
558
 
616
559
  Historically, this was not possible: the float label had to be placed after the input field to be targeted using the `input:focus + label` selector.
617
560
 
618
- \3c !--section-->*/
561
+ */
619
562
 
620
563
  /* Components */
564
+ /* Extends https://github.com/picocss/pico/blob/main/scss/content/_link.scss
565
+ \3c !--section:code-->
566
+ ```css */
567
+ /* Use inline flex only if link contains an icon */
568
+ a:not(.does-not-exist):has(> i) {
569
+ display: inline-flex;
570
+ overflow-y: clip; /* to work in pair with text-underline-offset in Safari */
571
+ gap: 0.375ch; /* =3/8 */
572
+ text-wrap: balance;
573
+ }
574
+ a:not(.does-not-exist) > i {
575
+ float: left; /* ✅ Chrome ❌ Safari */
576
+ font-style: normal;
577
+ text-underline-offset: -2em; /* ❌ Chrome ✅ Safari - to clip it with overflow-y */
578
+ }
579
+ /* Favicons */
580
+ a:not(.does-not-exist) > i > img {
581
+ display: inline-block; /* for Tailwind CSS Typography */
582
+ max-width: none; /* to keep ratio safe */
583
+ height: 1.25em;
584
+ margin-block-start: calc(-0.25em / 2);
585
+ }
586
+ a:not(.does-not-exist) > i[class^="fa-"],
587
+ a:not(.does-not-exist) > i[class*=" fa-"] {
588
+ line-height: inherit;
589
+ --fa-width: auto;
590
+ }
591
+ /* Font Awesome */
592
+ a:not(.does-not-exist) > i.fa-lg {
593
+ line-height: normal;
594
+ }
595
+ /*```
596
+ \3c !--section:summary-->
597
+
598
+ Use *Bl*ades `<i>`-helper to wrap emojis, favicons, or simply drop Font Awesome icons inside links.
599
+ It automatically handles sizing and alignment while preventing underline on icons.
600
+
601
+ \3c !--section:docs-->
602
+
603
+ <article class="breakout-item">
621
604
 
605
+ | Use `<i>`-helper with | Clean HTML without `<span>ning` |
606
+ | ------------------------------------------------------------------------------------- | ------------------------------------ |
607
+ | [<i>🥷</i> Emojis](#) | `<a><i>🥷</i> Text</a>` |
608
+ | [<i>![](https://www.google.com/s2/favicons?domain=any.digital&sz=64)</i> Favicons](#) | `<a><i><img src="..."></i> Text</a>` |
609
+ | [<i class="fa-brands fa-github fa-lg"></i> Font Awesome icons](#) | `<a><i class="fa-..."></i> Text</a>` |
610
+ </article>
611
+
612
+ Or even for:
613
+
614
+ <article>
615
+ <a href="#" style="margin: 0 0 0 1rem"><i>🥷</i> very-very-very-very-very-<br>long-multiline-links</a>
616
+ </article>
617
+
618
+ How we made it: https://codepen.io/editor/anydigital/pen/019d2b94-5616-75dc-a23e-e111869d5237
619
+
620
+ \3c !--section--> */
622
621
  /*\3c !--section:docs-->
623
622
 
624
623
  Use `dl.timeline` or wrap a `<dl>` with `.has-timeline` to style it as a
@@ -635,18 +634,16 @@ Mar 15, 2025
635
634
  </article>
636
635
 
637
636
  \3c !--section:code-->```css */
638
-
639
637
  dl.timeline dd, .has-timeline > dl dd {
640
638
  margin-block: 0;
641
639
  padding: 0.25rem 0 0.75rem 1rem;
642
- border-inline-start: 0.25rem solid gray;
640
+ border-inline-start: 0.25rem solid var(--blades-timeline-border-color);
643
641
  }
644
642
 
645
643
  /* Utilities */
646
-
647
644
  /*\3c !--section:docs-->
648
645
 
649
- ## Auto-dark
646
+ ## Dark auto
650
647
 
651
648
  `.dark-auto` automatically creates a simple dark version of any element:
652
649
 
@@ -656,31 +653,23 @@ dl.timeline dd, .has-timeline > dl dd {
656
653
 
657
654
  How it works:
658
655
  ```css */
659
-
660
656
  /* Per https://picocss.com/docs/css-variables#css-variables-for-color-schemes */
661
-
662
657
  :root {
663
658
  --blades-dark-filter: invert(100%) hue-rotate(180deg);
664
659
  }
665
-
666
660
  .dark-auto {
667
661
  /* Dark color scheme (Forced) */
668
662
  }
669
-
670
663
  /* Dark color scheme (Auto) */
671
-
672
664
  @media (prefers-color-scheme: dark) {
673
-
674
665
  .dark-auto {
675
666
  filter: var(--blades-dark-filter)
676
667
  }
677
668
  }
678
-
679
669
  .dark-auto[data-theme="dark"],
680
670
  .dark-auto:where([data-theme="dark"] *) {
681
671
  filter: var(--blades-dark-filter);
682
672
  }
683
-
684
673
  /*```
685
674
 
686
675
  ## Faded
@@ -693,28 +682,21 @@ How it works:
693
682
 
694
683
  How it works:
695
684
  ```css */
696
-
697
685
  .faded {
698
686
  opacity: 50%;
699
687
  }
700
-
701
688
  .faded:hover {
702
689
  opacity: 87.5%;
703
690
  }
704
-
705
691
  /*```
706
692
  \3c !--section--> */
707
693
 
708
694
  /* Fix the scrollbar color when inverted by https://tailwindcss.com/docs/filter-invert */
709
-
710
695
  .invert ::-webkit-scrollbar {
711
696
  filter: invert(1) !important;
712
697
  }
713
-
714
698
  /*\3c !--section:code-->```css */
715
-
716
699
  /* Default/fallback state */
717
-
718
700
  label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) > span,
719
701
  label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) label,
720
702
  .has-float-label > span,
@@ -724,7 +706,6 @@ label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"
724
706
  opacity: 75%;
725
707
  transition: all 0.25s;
726
708
  }
727
-
728
709
  label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) input,
729
710
  label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) textarea,
730
711
  label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) select,
@@ -735,24 +716,19 @@ label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"
735
716
  padding-inline-start: 1rem; /* match Pico */
736
717
  padding-block: 1.125rem 0.375rem; /* match Pico's total: 2 x 0.75rem = 1.5rem */
737
718
  }
738
-
739
719
  label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) input::-moz-placeholder, label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) textarea::-moz-placeholder, label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) select::-moz-placeholder, .has-float-label input::-moz-placeholder, .has-float-label textarea::-moz-placeholder, .has-float-label select::-moz-placeholder {
740
720
  opacity: 100%;
741
721
  -moz-transition: all 0.25s;
742
722
  transition: all 0.25s;
743
723
  }
744
-
745
724
  label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) input::placeholder, label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) textarea::placeholder, label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select) select::placeholder, .has-float-label input::placeholder, .has-float-label textarea::placeholder, .has-float-label select::placeholder {
746
725
  opacity: 100%;
747
726
  transition: all 0.25s;
748
727
  }
749
-
750
728
  /* Enlarged state */
751
-
752
729
  label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select):has(*:-moz-placeholder:not(:focus)) > span, label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select):has(*:-moz-placeholder:not(:focus)) label, .has-float-label:has(*:-moz-placeholder:not(:focus)) > span, .has-float-label:has(*:-moz-placeholder:not(:focus)) label {
753
730
  padding-block: 0.75rem; /* match Pico */
754
731
  }
755
-
756
732
  label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select):has(*:placeholder-shown:not(:focus)) > span,
757
733
  label:has(> span):has(input:not([type="checkbox"], [type="radio"], [type="range"]), textarea, select):has(*:placeholder-shown:not(:focus)) label,
758
734
  .has-float-label:has(*:placeholder-shown:not(:focus)) > span,
@@ -813,7 +789,6 @@ h5,
813
789
  h6 {
814
790
  text-wrap: balance;
815
791
  }
816
-
817
792
  h1 {
818
793
  margin-bottom: 1rem; /* for tw-typography */
819
794
  font-size: 2.5em; /* for pico.css & tw-typography */
@@ -858,15 +833,15 @@ table pre {
858
833
  margin-bottom: 0.25rem;
859
834
  }
860
835
 
861
- [data-jump-to="top"] {
836
+ [href="#top"] {
862
837
  opacity: 25%;
863
838
  }
864
839
 
865
- [data-jump-to="top"]:hover {
840
+ [href="#top"]:hover {
866
841
  opacity: 75%;
867
842
  }
868
843
 
869
- [data-jump-to="top"] > i {
844
+ [href="#top"] > i {
870
845
  display: inline-block;
871
846
  margin: 0.5rem;
872
847
  padding: 0.25rem 0.375rem;