@cahyo-dimas/freeday 3.9.4 → 3.10.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.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,55 @@ benar. Perubahan seperti itu ditulis di bawah `### Changed: BREAKING (types)`
9
9
  lama → tipe baru, dan cara menyempitkannya — bukan di bawah `### Added`, betapapun aditifnya dari
10
10
  sisi kit.
11
11
 
12
+ ## [3.10.0] - 2026-10-26
13
+
14
+ **Lima rilis dalam satu hari, empat di antaranya menambal satu kelas yang sama, dan pemiliknya
15
+ menghentikan polanya.** Setiap cacat Safari hari ini ditemukan oleh sebuah iPhone 7 sesudah
16
+ terkirim, satu per satu, karena suite kit berjalan di Chrome saja: `browser/harness.mjs` berbicara
17
+ CDP ke `chrome-headless-shell` dan tak ada WebKit di ujung kabel itu. Lantai dukungan di README
18
+ adalah prosa yang tak ada yang membacanya. Rilis ini memperbaiki keduanya.
19
+
20
+ ### Added
21
+ - **Gerbang lantai fitur** (`#091`), `test/baseline.test.mjs`. Ia **membaca ketiga angka lantai
22
+ dari README** alih-alih mengetiknya ulang, mengenumerasi 41 fitur CSS di kedua stylesheet yang
23
+ dikirim, dan gagal ketika satu di antaranya menuntut rilis yang lantainya tak capai. Pengecualian
24
+ boleh ada tetapi wajib membawa alasan tertulis minimal 160 karakter, DAN fiturnya harus benar-benar
25
+ masih melampaui lantai; kalau tidak gerbangnya menyuruh menghapus entrinya. Daftar pemaafan yang
26
+ tumbuh tanpa alasan adalah penjaga yang berhenti menjaga, dan itu persis kegagalan yang sudah
27
+ terjadi dua kali di hari yang sama.
28
+ - **Suite WebKit di gerbang** (`#092`), job CI tersendiri dengan Playwright, terpisah penuh dari
29
+ harness CDP yang menjalankan 206 uji dan tidak menyentuhnya. Sebelas uji menutup apa yang hanya
30
+ WebKit dapat perlihatkan: backdrop dan centang checkbox dan thumb switch harus MENCAPAI keadaan
31
+ akhirnya di bawah Reduce Motion, tap pada backdrop menutup drawer (sintesis klik `#057`), dan
32
+ asap tata letak telepon. Ia menolak lulus diam-diam lewat lima jalur yang semuanya diuji terhadap
33
+ laporan JSON nyata: laporan hilang, nol uji, ada yang dilewati, ada yang goyah, atau uji identitas
34
+ mesin hilang.
35
+
36
+ ### Fixed
37
+ - **Tiga fitur melampaui lantai yang kit nyatakan sendiri** (`#091`). `@container` di CFL dipagari
38
+ `@supports` dengan cadangan `@media (max-width:32rem)`, dan angka itu diturunkan dari lebar
39
+ dialognya sendiri bukan dipilih; absennya bukan kosmetik, sebab di Safari 15.4 sampai 15.8 seluruh
40
+ bloknya dibuang dan dialog 372px memakai tabel tiga kolom yang digulir dua arah.
41
+ `overscroll-behavior` dan `text-wrap:balance` jadi pengecualian beralasan. **Lantainya tidak
42
+ dinaikkan:** iPhone 7 yang menemukan empat cacat hari ini berhenti di iOS 15.
43
+ - **`user-select` dikirim tanpa pasangan `-webkit-`** di `.fdy-avatar` dan `.fdy-breadcrumb__sep`
44
+ (`#091`). WebKit menuntut prefiks itu sampai Safari 17.
45
+ - **Blok durasi-mungil lama di `src/base.css` mengalahkan perlakuan reduced-motion kit sendiri**
46
+ (`#093`). `*,*::before,*::after{animation-duration:.001ms !important}` sebagai `!important` pada
47
+ `*` menang atas `.fdy-spinner{animation-duration:2s}`, `.fdy-skeleton{animation:none}` dan
48
+ `.fdy-progress{animation:none}`, jadi ketiga perlakuan yang ditulis khusus untuk setelan itu tidak
49
+ pernah bekerja. Ketiganya satu-satunya animasi di luar pagar `no-preference`, jadi membuang blok
50
+ lama memulihkan semuanya tanpa membuka apa pun. Penjaga 3.9.4 melewatkannya karena regexnya
51
+ menuntut `0.001ms` berawalan nol sementara berkasnya menulis `.001ms`; sekarang ia menerima
52
+ keduanya dan ikut memeriksa `animation-duration`.
53
+
54
+ ### Docs
55
+ - **Meta viewport dinyatakan sebagai syarat**, bukan ditampilkan di contoh. Tanpanya mesin mobile
56
+ menata halaman di lebar bawaan sekitar 980px, `(max-width:720px)` tak pernah cocok, dan seluruh
57
+ lapisan responsif kit absen: shell berdiri dalam bentuk desktopnya di telepon. Itu bukan
58
+ hipotetis. Fixture tanpa tag itu melaporkan `window.innerWidth` 981 pada deskriptor iPhone 7 di
59
+ jalanan pertama suite WebKit, bentuk yang sama persis dengan laporan yang memulai suite itu.
60
+
12
61
  ## [3.9.4] - 2026-10-26
13
62
 
14
63
  **Sembilan dari lima puluh enam, dan empat puluh delapan sisanya ranjau.** Tiga rilis berturut-turut
package/README.id.md CHANGED
@@ -5,7 +5,7 @@
5
5
  > **Lebih banyak _free day_ buat dev. UI kit-nya sudah siap pakai.**
6
6
 
7
7
  [![Live docs](https://img.shields.io/badge/docs-live-2050d8?style=flat-square)](https://cahyo-dimas.github.io/freeday-ui-kit/)
8
- [![Release](https://img.shields.io/badge/release-v3.9.4-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.9.4)
8
+ [![Release](https://img.shields.io/badge/release-v3.10.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.10.0)
9
9
 
10
10
  UI KIT yang token-driven & framework-agnostic. Satu sumber kebenaran untuk warna, tipografi,
11
11
  spasi, dan komponen. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
@@ -256,8 +256,14 @@ terlihat, HTML native dulu sebelum ARIA, komponen interaktif ikut pola WAI-ARIA
256
256
 
257
257
  ## Dukungan browser
258
258
  **Chrome 105 · Safari 15.4 · Firefox 121.** Floor-nya ditentukan `:has()`, dan oleh generasi Safari
259
- 15.4 untuk native `<dialog>`/`::backdrop`, `accent-color`, dan `100dvh`. `conic-gradient` dan
260
- `scroll-snap` lebih tua lagi.
259
+ 15.4 untuk native `<dialog>`/`::backdrop` dan `accent-color`. `conic-gradient` dan `scroll-snap`
260
+ lebih tua lagi.
261
+
262
+ `test/baseline.test.mjs` membaca tiga angka itu dari baris di atas, mengenumerasi fitur CSS yang
263
+ dipakai kedua stylesheet yang dikirim, dan gagal kalau salah satunya butuh rilis yang tak dijangkau
264
+ floor itu. Sebelum uji itu ada, floor-nya cuma kalimat yang tak bisa ditegakkan siapa pun, dan di
265
+ hari uji ini ditulis sudah ada lima rilis keluar dalam satu hari, empat di antaranya menambal cacat
266
+ Safari yang ketemu di iPhone 7 sesudah terkirim.
261
267
 
262
268
  Dulu floor itu ditentukan `color-mix()` di Safari 16.4, dan sekarang tidak lagi. Harganya memang tak
263
269
  pernah pantas: deklarasi yang mencampur sebuah token membawa `var()`, jadi ia dianggap sah saat
@@ -268,11 +274,33 @@ sekarang ditulis `rgba(var(--color-primary-rgb), .26)`, warnanya sama dan sudah
268
274
  Safari 9; empat aturan yang memang tak bisa dijawab secara statis membawa pasangan `@supports` yang
269
275
  sungguhan. `test/color-mix.test.mjs` menjaga agar yang berikutnya tetap jujur.
270
276
 
271
- Yang paling baru dipakai kit ini (`@container`, `@starting-style`, `text-wrap`) sifatnya peningkatan:
272
- browser yang belum punya akan melewati bloknya dan tetap memakai aturan tanpa syarat di bawahnya.
273
- Blur backdrop pakai `-webkit-backdrop-filter` + `backdrop-filter` (Safari lama tetap jalan, hanya
274
- tanpa blur). Belum ada CSS build/autoprefixer, jadi dukungan di bawah floor itu jadi tanggung jawab
275
- konsumen.
277
+ Yang paling baru dipakai kit ini memang melewati floor itu, dengan sengaja, dan tiap satunya
278
+ terdaftar di `test/baseline.test.mjs` beserta apa yang hilang di browser di bawah floor:
279
+
280
+ - `@container` di dialog choose-from-list dipagari `@supports`, dan satu media query lebar viewport
281
+ menumpuk barisnya di bawahnya. Yang tak bisa dilihat cadangan itu adalah dialog sempit di dalam
282
+ viewport lebar, yaitu panel yang didok di desktop, dan di situ dialognya tetap memakai tabel yang
283
+ digulir seperti selama ini.
284
+ - `@starting-style` dan transisi `allow-discrete` menganimasikan modal dan drawer. Di bawah floor
285
+ at-rule-nya dilewati dan shorthand-nya dibuang saat parse, jadi keduanya buka dan tutup tanpa
286
+ animasi, selalu di keadaan akhirnya dan tak pernah separuh jalan.
287
+ - `text-wrap: balance` menyeimbangkan dua kelas judul. Tanpa itu judulnya patah di tempat ia patah
288
+ sebelumnya.
289
+ - `overscroll-behavior: contain` menghentikan gulir di ujung nav dan di dua rail halaman yang
290
+ sticky. Tanpa itu gulirnya lanjut ke halaman di belakangnya, seperti sebelum properti ini ada.
291
+ Tidak ada gantinya di CSS di bawah Safari 16; pilihan lainnya scroll lock di JS, dan shell ini
292
+ sengaja tidak melakukannya.
293
+ - `scrollbar-width: none` menyembunyikan scrollbar carousel, dengan `::-webkit-scrollbar` di
294
+ sebelahnya yang mengerjakan hal sama untuk setiap WebKit di dalam floor.
295
+ - `100dvh` membatasi tinggi rail sticky di layar dari 960px. Safari sudah punya unit itu sejak rilis
296
+ floor-nya sendiri, jadi satu-satunya mesin di dalam floor yang belum punya adalah tiga rilis
297
+ Chrome sebelum unit itu mendarat di sana, dan di situ batasnya hilang dan aside yang panjang
298
+ tumbuh melewati viewport alih-alih digulir di dalam dirinya.
299
+
300
+ Blur backdrop pakai `-webkit-backdrop-filter` + `backdrop-filter`, dan `user-select` membawa kembar
301
+ `-webkit-`-nya dengan cara yang sama. Uji yang sama menegaskan kedua pasangan itu aturan per aturan,
302
+ jadi properti standarnya tak pernah dikirim sendirian. Belum ada CSS build/autoprefixer, jadi
303
+ dukungan di bawah floor itu jadi tanggung jawab konsumen.
276
304
 
277
305
  ## Lisensi
278
306
  [MIT](LICENSE) © 2026 Cahyo D. Kurnianto. Bebas dipakai, diubah, dan didistribusikan asal
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  > **More free days for devs. The UI kit is ready to use.**
6
6
 
7
7
  [![Live docs](https://img.shields.io/badge/docs-live-2050d8?style=flat-square)](https://cahyo-dimas.github.io/freeday-ui-kit/)
8
- [![Release](https://img.shields.io/badge/release-v3.9.4-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.9.4)
8
+ [![Release](https://img.shields.io/badge/release-v3.10.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.10.0)
9
9
 
10
10
  A token-driven, framework-agnostic UI kit. One source of truth for color, typography,
11
11
  spacing, and components. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
@@ -266,8 +266,14 @@ the "Accessibility" section in `docs/index.html`.
266
266
 
267
267
  ## Browser support
268
268
  **Chrome 105 · Safari 15.4 · Firefox 121.** The floor is set by `:has()`, and by the Safari 15.4
269
- generation of native `<dialog>`/`::backdrop`, `accent-color` and `100dvh`. `conic-gradient` and
270
- `scroll-snap` are older still.
269
+ generation of native `<dialog>`/`::backdrop` and `accent-color`. `conic-gradient` and `scroll-snap`
270
+ are older still.
271
+
272
+ `test/baseline.test.mjs` reads those three numbers out of the line above, enumerates the CSS
273
+ features both shipped stylesheets use, and fails when one of them needs a release the floor does not
274
+ reach. Until that test existed the floor was a sentence nobody could enforce, and the day it was
275
+ written five releases had gone out in one day, four of them fixing Safari defects found on an
276
+ iPhone 7 after shipping.
271
277
 
272
278
  `color-mix()` used to set that floor at Safari 16.4, and it no longer does. It was never worth it:
273
279
  a declaration that mixes a token carries `var()`, so it is assumed valid at parse time and only
@@ -278,11 +284,32 @@ written `rgba(var(--color-primary-rgb), .26)`, which is the same colour and pars
278
284
  the four rules that cannot be answered statically carry a real `@supports` counterpart.
279
285
  `test/color-mix.test.mjs` keeps the next one honest.
280
286
 
281
- The newest things the kit touches (`@container`, `@starting-style`, `text-wrap`) are enhancements: a
282
- browser without them skips the block and keeps the unconditional rule underneath. Backdrop blur uses
283
- `-webkit-backdrop-filter` + `backdrop-filter` (older Safari still works, just without the blur).
284
- There's no CSS build or autoprefixer step, so supporting anything below the floor is the consumer's
285
- responsibility.
287
+ The newest things the kit touches reach past that floor on purpose, and each one is listed in
288
+ `test/baseline.test.mjs` with what a browser below the floor loses:
289
+
290
+ - `@container` in the choose-from-list dialog is fenced with `@supports`, and a viewport-width media
291
+ query stacks the rows underneath it. The case the fallback cannot see is a narrow dialog inside a
292
+ wide viewport, which is a docked panel on a desktop, and there the dialog keeps the scrolling
293
+ table it always had.
294
+ - `@starting-style` and the `allow-discrete` transitions animate the modal and the drawer. Below the
295
+ floor the at-rule is skipped and the shorthand is dropped at parse time, so both open and close
296
+ with no animation, always at their end state and never halfway.
297
+ - `text-wrap: balance` balances two heading classes. Without it a heading wraps where it used to.
298
+ - `overscroll-behavior: contain` stops a scroll at the end of the nav and of the sticky page rails.
299
+ Without it that scroll carries on into the page behind, the way it did before the property
300
+ existed. There is no CSS substitute below Safari 16; the alternative is a scroll lock in JS, which
301
+ this shell deliberately does not do.
302
+ - `scrollbar-width: none` hides the carousel scrollbar, with `::-webkit-scrollbar` beside it doing
303
+ the same job for every WebKit inside the floor.
304
+ - `100dvh` caps the sticky rails on screens from 960px. Safari has had the unit since the floor
305
+ release itself, so the only engines inside the floor without it are the three Chrome releases
306
+ before it landed there, and on those the cap goes away and a long aside grows past the viewport
307
+ instead of scrolling inside itself.
308
+
309
+ Backdrop blur uses `-webkit-backdrop-filter` + `backdrop-filter`, and `user-select` carries its
310
+ `-webkit-` twin the same way. The same test asserts both pairings rule by rule, so the standard
311
+ property never ships alone. There's no CSS build or autoprefixer step, so supporting anything below
312
+ the floor is the consumer's responsibility.
286
313
 
287
314
  ## License
288
315
  [MIT](LICENSE) © 2026 Cahyo D. Kurnianto. Free to use, modify, and distribute, as long as the
@@ -1213,9 +1213,6 @@ a { color: var(--color-primary-strong); }
1213
1213
  * framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
1214
1214
  * bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
1215
1215
  .fdy-list-reset { list-style: none; margin: 0; padding: 0; }
1216
- @media (prefers-reduced-motion: reduce) {
1217
- *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
1218
- }
1219
1216
 
1220
1217
  /* ===========================================================================
1221
1218
  * Reduce Motion: SETIAP transisi dinetralkan, bukan daftar yang dipelihara tangan.
@@ -1495,7 +1492,7 @@ a { color: var(--color-primary-strong); }
1495
1492
  @media (prefers-reduced-motion:no-preference){.fdy-autocomplete__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
1496
1493
 
1497
1494
  /* Freeday. Avatar */
1498
- .fdy-avatar{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.5rem;height:2.5rem;border-radius:var(--radius-full);background:var(--color-primary-soft);color:var(--color-primary-strong);font-family:var(--font-body);font-weight:var(--weight-semibold);font-size:var(--text-sm);line-height:1;overflow:hidden;user-select:none;}
1495
+ .fdy-avatar{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.5rem;height:2.5rem;border-radius:var(--radius-full);background:var(--color-primary-soft);color:var(--color-primary-strong);font-family:var(--font-body);font-weight:var(--weight-semibold);font-size:var(--text-sm);line-height:1;overflow:hidden;-webkit-user-select:none;user-select:none;}
1499
1496
  .fdy-avatar img{width:100%;height:100%;object-fit:cover;}
1500
1497
  /* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
1501
1498
  `--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
@@ -1651,7 +1648,7 @@ a { color: var(--color-primary-strong); }
1651
1648
  .fdy-breadcrumb__link:hover{color:var(--color-primary-strong);text-decoration:underline;}
1652
1649
  .fdy-breadcrumb__link:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
1653
1650
  .fdy-breadcrumb__item[aria-current="page"]{color:var(--color-text);font-weight:var(--weight-medium);}
1654
- .fdy-breadcrumb__sep{color:var(--color-text-subtle);user-select:none;}
1651
+ .fdy-breadcrumb__sep{color:var(--color-text-subtle);-webkit-user-select:none;user-select:none;}
1655
1652
 
1656
1653
  /* Freeday. Responsive display utilities (pairs with freeday-breakpoint.js).
1657
1654
  * Breakpoint scale (min-width, px): sm 600 · md 960 · lg 1280 · xl 1920.
@@ -1957,7 +1954,13 @@ a { color: var(--color-primary-strong); }
1957
1954
  * size, which is what made `.fdy-stats--inline` collapse to three zero tracks in 3.1.0. This box
1958
1955
  * takes its width from the dialog (`min(46rem, 100vw - …)`, an explicit width) and already scrolls
1959
1956
  * its own content, so it never sized to the table to begin with. */
1960
- .fdy-cfl__results{container:fdy-cfl / inline-size;overflow:auto;max-height:min(24rem,50vh);}
1957
+ .fdy-cfl__results{overflow:auto;max-height:min(24rem,50vh);}
1958
+ /* Fenced the way composition.css:62 fences its own, so the declaration states the condition it
1959
+ needs instead of relying on a browser to drop it. Safari 15.4 to 15.8 is inside the floor the
1960
+ README states and has no container queries at all. */
1961
+ @supports (container-type:inline-size){
1962
+ .fdy-cfl__results{container:fdy-cfl / inline-size;}
1963
+ }
1961
1964
  .fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
1962
1965
 
1963
1966
  .fdy-cfl__row{cursor:pointer;}
@@ -1999,30 +2002,71 @@ a { color: var(--color-primary-strong); }
1999
2002
  *
2000
2003
  * Column headers ARE a real loss here, and a deliberate one: `<thead>` is hidden, because a header
2001
2004
  * row that costs a third of the width buys nothing when each row is labelled by its own title. */
2002
- @container fdy-cfl (max-width: 30rem){
2003
- .fdy-cfl__results .fdy-table,
2004
- .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
2005
- .fdy-cfl__results .fdy-table thead{display:none;}
2006
- .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
2007
- .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
2008
- .fdy-cfl__row > td{display:block;padding:0;border:0;}
2009
- /* The title takes the whole first line, which is what pushes every remaining cell onto the second
2010
- one — no selector has to know how many columns there are, or which of them is last. */
2011
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
2012
- .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
2013
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
2014
- /* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
2015
- .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
2016
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
2017
- .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
2018
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
2019
- .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
2020
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
2021
- .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
2022
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
2023
- .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
2024
- /* The checkbox keeps its own line position beside the title rather than above it. */
2025
- .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
2005
+ @supports (container-type:inline-size){
2006
+ @container fdy-cfl (max-width: 30rem){
2007
+ .fdy-cfl__results .fdy-table,
2008
+ .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
2009
+ .fdy-cfl__results .fdy-table thead{display:none;}
2010
+ .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
2011
+ .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
2012
+ .fdy-cfl__row > td{display:block;padding:0;border:0;}
2013
+ /* The title takes the whole first line, which is what pushes every remaining cell onto the second
2014
+ one — no selector has to know how many columns there are, or which of them is last. */
2015
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
2016
+ .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
2017
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
2018
+ /* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
2019
+ .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
2020
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
2021
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
2022
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
2023
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
2024
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
2025
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
2026
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
2027
+ .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
2028
+ /* The checkbox keeps its own line position beside the title rather than above it. */
2029
+ .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
2030
+ }
2031
+ }
2032
+
2033
+ /* The same block for a browser that has no container queries, which is every Safari from 15.4 to
2034
+ * 15.8 and inside the floor the README states. There the @supports above is false, the stacked
2035
+ * layout never applied, and a 372px dialog kept a three-column master-data table that scrolls on
2036
+ * both axes.
2037
+ *
2038
+ * The condition is a VIEWPORT width, the only width this path can read. 32rem is derived from the
2039
+ * dialog rather than picked: the dialog is min(46rem, 100vw - var(--space-8)) with --space-8 at
2040
+ * 2rem, and the results box takes that width, so it sits at or under 30rem exactly when the
2041
+ * viewport sits at or under 32rem. What a media query cannot answer is the case the container
2042
+ * query exists for, a narrow dialog inside a wide viewport, which is a docked panel on a desktop;
2043
+ * on those browsers that one case keeps the scrolling table it has always had.
2044
+ *
2045
+ * The body is a copy of the body above, because CSS has no way to share one declaration block
2046
+ * between two different conditions. test/baseline.test.mjs compares the two, so an edit that
2047
+ * reaches one of them and misses the other fails there. */
2048
+ @supports not (container-type:inline-size){
2049
+ @media (max-width:32rem){
2050
+ .fdy-cfl__results .fdy-table,
2051
+ .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
2052
+ .fdy-cfl__results .fdy-table thead{display:none;}
2053
+ .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
2054
+ .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
2055
+ .fdy-cfl__row > td{display:block;padding:0;border:0;}
2056
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
2057
+ .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
2058
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
2059
+ .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
2060
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
2061
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
2062
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
2063
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
2064
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
2065
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
2066
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
2067
+ .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
2068
+ .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
2069
+ }
2026
2070
  }
2027
2071
 
2028
2072
  /* Freeday. Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
package/dist/freeday.css CHANGED
@@ -36,9 +36,6 @@ a { color: var(--color-primary-strong); }
36
36
  * framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
37
37
  * bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
38
38
  .fdy-list-reset { list-style: none; margin: 0; padding: 0; }
39
- @media (prefers-reduced-motion: reduce) {
40
- *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
41
- }
42
39
 
43
40
  /* ===========================================================================
44
41
  * Reduce Motion: SETIAP transisi dinetralkan, bukan daftar yang dipelihara tangan.
@@ -318,7 +315,7 @@ a { color: var(--color-primary-strong); }
318
315
  @media (prefers-reduced-motion:no-preference){.fdy-autocomplete__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
319
316
 
320
317
  /* Freeday. Avatar */
321
- .fdy-avatar{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.5rem;height:2.5rem;border-radius:var(--radius-full);background:var(--color-primary-soft);color:var(--color-primary-strong);font-family:var(--font-body);font-weight:var(--weight-semibold);font-size:var(--text-sm);line-height:1;overflow:hidden;user-select:none;}
318
+ .fdy-avatar{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.5rem;height:2.5rem;border-radius:var(--radius-full);background:var(--color-primary-soft);color:var(--color-primary-strong);font-family:var(--font-body);font-weight:var(--weight-semibold);font-size:var(--text-sm);line-height:1;overflow:hidden;-webkit-user-select:none;user-select:none;}
322
319
  .fdy-avatar img{width:100%;height:100%;object-fit:cover;}
323
320
  /* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
324
321
  `--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
@@ -474,7 +471,7 @@ a { color: var(--color-primary-strong); }
474
471
  .fdy-breadcrumb__link:hover{color:var(--color-primary-strong);text-decoration:underline;}
475
472
  .fdy-breadcrumb__link:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
476
473
  .fdy-breadcrumb__item[aria-current="page"]{color:var(--color-text);font-weight:var(--weight-medium);}
477
- .fdy-breadcrumb__sep{color:var(--color-text-subtle);user-select:none;}
474
+ .fdy-breadcrumb__sep{color:var(--color-text-subtle);-webkit-user-select:none;user-select:none;}
478
475
 
479
476
  /* Freeday. Responsive display utilities (pairs with freeday-breakpoint.js).
480
477
  * Breakpoint scale (min-width, px): sm 600 · md 960 · lg 1280 · xl 1920.
@@ -780,7 +777,13 @@ a { color: var(--color-primary-strong); }
780
777
  * size, which is what made `.fdy-stats--inline` collapse to three zero tracks in 3.1.0. This box
781
778
  * takes its width from the dialog (`min(46rem, 100vw - …)`, an explicit width) and already scrolls
782
779
  * its own content, so it never sized to the table to begin with. */
783
- .fdy-cfl__results{container:fdy-cfl / inline-size;overflow:auto;max-height:min(24rem,50vh);}
780
+ .fdy-cfl__results{overflow:auto;max-height:min(24rem,50vh);}
781
+ /* Fenced the way composition.css:62 fences its own, so the declaration states the condition it
782
+ needs instead of relying on a browser to drop it. Safari 15.4 to 15.8 is inside the floor the
783
+ README states and has no container queries at all. */
784
+ @supports (container-type:inline-size){
785
+ .fdy-cfl__results{container:fdy-cfl / inline-size;}
786
+ }
784
787
  .fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
785
788
 
786
789
  .fdy-cfl__row{cursor:pointer;}
@@ -822,30 +825,71 @@ a { color: var(--color-primary-strong); }
822
825
  *
823
826
  * Column headers ARE a real loss here, and a deliberate one: `<thead>` is hidden, because a header
824
827
  * row that costs a third of the width buys nothing when each row is labelled by its own title. */
825
- @container fdy-cfl (max-width: 30rem){
826
- .fdy-cfl__results .fdy-table,
827
- .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
828
- .fdy-cfl__results .fdy-table thead{display:none;}
829
- .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
830
- .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
831
- .fdy-cfl__row > td{display:block;padding:0;border:0;}
832
- /* The title takes the whole first line, which is what pushes every remaining cell onto the second
833
- one — no selector has to know how many columns there are, or which of them is last. */
834
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
835
- .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
836
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
837
- /* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
838
- .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
839
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
840
- .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
841
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
842
- .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
843
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
844
- .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
845
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
846
- .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
847
- /* The checkbox keeps its own line position beside the title rather than above it. */
848
- .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
828
+ @supports (container-type:inline-size){
829
+ @container fdy-cfl (max-width: 30rem){
830
+ .fdy-cfl__results .fdy-table,
831
+ .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
832
+ .fdy-cfl__results .fdy-table thead{display:none;}
833
+ .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
834
+ .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
835
+ .fdy-cfl__row > td{display:block;padding:0;border:0;}
836
+ /* The title takes the whole first line, which is what pushes every remaining cell onto the second
837
+ one — no selector has to know how many columns there are, or which of them is last. */
838
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
839
+ .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
840
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
841
+ /* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
842
+ .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
843
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
844
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
845
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
846
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
847
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
848
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
849
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
850
+ .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
851
+ /* The checkbox keeps its own line position beside the title rather than above it. */
852
+ .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
853
+ }
854
+ }
855
+
856
+ /* The same block for a browser that has no container queries, which is every Safari from 15.4 to
857
+ * 15.8 and inside the floor the README states. There the @supports above is false, the stacked
858
+ * layout never applied, and a 372px dialog kept a three-column master-data table that scrolls on
859
+ * both axes.
860
+ *
861
+ * The condition is a VIEWPORT width, the only width this path can read. 32rem is derived from the
862
+ * dialog rather than picked: the dialog is min(46rem, 100vw - var(--space-8)) with --space-8 at
863
+ * 2rem, and the results box takes that width, so it sits at or under 30rem exactly when the
864
+ * viewport sits at or under 32rem. What a media query cannot answer is the case the container
865
+ * query exists for, a narrow dialog inside a wide viewport, which is a docked panel on a desktop;
866
+ * on those browsers that one case keeps the scrolling table it has always had.
867
+ *
868
+ * The body is a copy of the body above, because CSS has no way to share one declaration block
869
+ * between two different conditions. test/baseline.test.mjs compares the two, so an edit that
870
+ * reaches one of them and misses the other fails there. */
871
+ @supports not (container-type:inline-size){
872
+ @media (max-width:32rem){
873
+ .fdy-cfl__results .fdy-table,
874
+ .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
875
+ .fdy-cfl__results .fdy-table thead{display:none;}
876
+ .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
877
+ .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
878
+ .fdy-cfl__row > td{display:block;padding:0;border:0;}
879
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
880
+ .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
881
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
882
+ .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
883
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
884
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
885
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
886
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
887
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
888
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
889
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
890
+ .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
891
+ .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
892
+ }
849
893
  }
850
894
 
851
895
  /* Freeday. Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
@@ -156,6 +156,13 @@ cp -r node_modules/@cahyo-dimas/freeday/dist ./assets/freeday # copy into your
156
156
  ```
157
157
  > Two-file alternative: `freeday.tokens.css` (tokens) + `freeday.css` (components).
158
158
 
159
+ > **The viewport meta is required, not decoration.** Without it a mobile engine lays the page out at
160
+ > a default width near 980px, so `(max-width: 720px)` never matches and the kit's entire responsive
161
+ > layer is absent: the app shell stands in its desktop form on a phone, sidebar visible and bottom
162
+ > nav gone. That is not a hypothetical. It is what a fixture without the tag did on the first run of
163
+ > the WebKit suite, reporting `window.innerWidth` 981 on an iPhone 7 descriptor, and it is the same
164
+ > shape as the report that started that suite.
165
+
159
166
  ### 3. Load the enhancers before `</body>`
160
167
  ```html
161
168
  <script src="assets/freeday/freeday.js" defer></script>
@@ -191,7 +198,7 @@ live docs also have a copy button per component.
191
198
  ```bash
192
199
  npm i @cahyo-dimas/freeday
193
200
  ```
194
- Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.9.4"` (public npm package). `dist/` is
201
+ Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.10.0"` (public npm package). `dist/` is
195
202
  committed and published → no build step; `npm ci` runs without auth.
196
203
 
197
204
  ### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cahyo-dimas/freeday",
3
- "version": "3.9.4",
3
+ "version": "3.10.0",
4
4
  "description": "Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -101,9 +101,11 @@
101
101
  "version": "node tokens/build.mjs && git add dist",
102
102
  "typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit",
103
103
  "test:blazor": "dotnet test test/blazor -v quiet --nologo",
104
+ "test:webkit": "playwright test",
104
105
  "test:blazor-server": "node --test browser/blazor-server.mjs"
105
106
  },
106
107
  "devDependencies": {
108
+ "@playwright/test": "^1.64.1",
107
109
  "@types/react": "^19.2.17",
108
110
  "esbuild": "^0.28.1",
109
111
  "react": "^19.2.8",
package/src/base.css CHANGED
@@ -35,9 +35,6 @@ a { color: var(--color-primary-strong); }
35
35
  * framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
36
36
  * bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
37
37
  .fdy-list-reset { list-style: none; margin: 0; padding: 0; }
38
- @media (prefers-reduced-motion: reduce) {
39
- *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
40
- }
41
38
 
42
39
  /* ===========================================================================
43
40
  * Reduce Motion: SETIAP transisi dinetralkan, bukan daftar yang dipelihara tangan.
@@ -1,5 +1,5 @@
1
1
  /* Freeday. Avatar */
2
- .fdy-avatar{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.5rem;height:2.5rem;border-radius:var(--radius-full);background:var(--color-primary-soft);color:var(--color-primary-strong);font-family:var(--font-body);font-weight:var(--weight-semibold);font-size:var(--text-sm);line-height:1;overflow:hidden;user-select:none;}
2
+ .fdy-avatar{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.5rem;height:2.5rem;border-radius:var(--radius-full);background:var(--color-primary-soft);color:var(--color-primary-strong);font-family:var(--font-body);font-weight:var(--weight-semibold);font-size:var(--text-sm);line-height:1;overflow:hidden;-webkit-user-select:none;user-select:none;}
3
3
  .fdy-avatar img{width:100%;height:100%;object-fit:cover;}
4
4
  /* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
5
5
  `--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
@@ -6,4 +6,4 @@
6
6
  .fdy-breadcrumb__link:hover{color:var(--color-primary-strong);text-decoration:underline;}
7
7
  .fdy-breadcrumb__link:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(var(--color-primary-rgb),.26);}
8
8
  .fdy-breadcrumb__item[aria-current="page"]{color:var(--color-text);font-weight:var(--weight-medium);}
9
- .fdy-breadcrumb__sep{color:var(--color-text-subtle);user-select:none;}
9
+ .fdy-breadcrumb__sep{color:var(--color-text-subtle);-webkit-user-select:none;user-select:none;}
@@ -23,7 +23,13 @@
23
23
  * size, which is what made `.fdy-stats--inline` collapse to three zero tracks in 3.1.0. This box
24
24
  * takes its width from the dialog (`min(46rem, 100vw - …)`, an explicit width) and already scrolls
25
25
  * its own content, so it never sized to the table to begin with. */
26
- .fdy-cfl__results{container:fdy-cfl / inline-size;overflow:auto;max-height:min(24rem,50vh);}
26
+ .fdy-cfl__results{overflow:auto;max-height:min(24rem,50vh);}
27
+ /* Fenced the way composition.css:62 fences its own, so the declaration states the condition it
28
+ needs instead of relying on a browser to drop it. Safari 15.4 to 15.8 is inside the floor the
29
+ README states and has no container queries at all. */
30
+ @supports (container-type:inline-size){
31
+ .fdy-cfl__results{container:fdy-cfl / inline-size;}
32
+ }
27
33
  .fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
28
34
 
29
35
  .fdy-cfl__row{cursor:pointer;}
@@ -65,28 +71,69 @@
65
71
  *
66
72
  * Column headers ARE a real loss here, and a deliberate one: `<thead>` is hidden, because a header
67
73
  * row that costs a third of the width buys nothing when each row is labelled by its own title. */
68
- @container fdy-cfl (max-width: 30rem){
69
- .fdy-cfl__results .fdy-table,
70
- .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
71
- .fdy-cfl__results .fdy-table thead{display:none;}
72
- .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
73
- .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
74
- .fdy-cfl__row > td{display:block;padding:0;border:0;}
75
- /* The title takes the whole first line, which is what pushes every remaining cell onto the second
76
- one — no selector has to know how many columns there are, or which of them is last. */
77
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
78
- .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
79
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
80
- /* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
81
- .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
82
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
83
- .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
84
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
85
- .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
86
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
87
- .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
88
- .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
89
- .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
90
- /* The checkbox keeps its own line position beside the title rather than above it. */
91
- .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
74
+ @supports (container-type:inline-size){
75
+ @container fdy-cfl (max-width: 30rem){
76
+ .fdy-cfl__results .fdy-table,
77
+ .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
78
+ .fdy-cfl__results .fdy-table thead{display:none;}
79
+ .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
80
+ .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
81
+ .fdy-cfl__row > td{display:block;padding:0;border:0;}
82
+ /* The title takes the whole first line, which is what pushes every remaining cell onto the second
83
+ one — no selector has to know how many columns there are, or which of them is last. */
84
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
85
+ .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
86
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
87
+ /* Multi-select: the checkbox shares line one with the title, so the title claims the rest of it. */
88
+ .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
89
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
90
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
91
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
92
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
93
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
94
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
95
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
96
+ .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
97
+ /* The checkbox keeps its own line position beside the title rather than above it. */
98
+ .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
99
+ }
100
+ }
101
+
102
+ /* The same block for a browser that has no container queries, which is every Safari from 15.4 to
103
+ * 15.8 and inside the floor the README states. There the @supports above is false, the stacked
104
+ * layout never applied, and a 372px dialog kept a three-column master-data table that scrolls on
105
+ * both axes.
106
+ *
107
+ * The condition is a VIEWPORT width, the only width this path can read. 32rem is derived from the
108
+ * dialog rather than picked: the dialog is min(46rem, 100vw - var(--space-8)) with --space-8 at
109
+ * 2rem, and the results box takes that width, so it sits at or under 30rem exactly when the
110
+ * viewport sits at or under 32rem. What a media query cannot answer is the case the container
111
+ * query exists for, a narrow dialog inside a wide viewport, which is a docked panel on a desktop;
112
+ * on those browsers that one case keeps the scrolling table it has always had.
113
+ *
114
+ * The body is a copy of the body above, because CSS has no way to share one declaration block
115
+ * between two different conditions. test/baseline.test.mjs compares the two, so an edit that
116
+ * reaches one of them and misses the other fails there. */
117
+ @supports not (container-type:inline-size){
118
+ @media (max-width:32rem){
119
+ .fdy-cfl__results .fdy-table,
120
+ .fdy-cfl__results .fdy-table tbody{display:block;width:100%;}
121
+ .fdy-cfl__results .fdy-table thead{display:none;}
122
+ .fdy-cfl__row{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-2);padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);}
123
+ .fdy-cfl__results .fdy-table tbody tr:last-child{border-bottom:0;}
124
+ .fdy-cfl__row > td{display:block;padding:0;border:0;}
125
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check),
126
+ .fdy-cfl__row > .fdy-cfl__check + td{font-weight:var(--weight-semibold);color:var(--color-text);}
127
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check){flex:0 0 100%;}
128
+ .fdy-cfl__row > .fdy-cfl__check + td{flex:1 0 calc(100% - var(--space-6));}
129
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td,
130
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td{font-size:var(--text-xs);color:var(--color-text-muted);}
131
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td:empty,
132
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td:empty{display:none;}
133
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) ~ td::before,
134
+ .fdy-cfl__row > .fdy-cfl__check + td ~ td::before{content:"·";margin-right:var(--space-2);color:var(--color-text-subtle);}
135
+ .fdy-cfl__row > td:first-child:not(.fdy-cfl__check) + td::before,
136
+ .fdy-cfl__row > .fdy-cfl__check + td + td::before{content:none;}
137
+ .fdy-cfl__row > .fdy-cfl__check{flex:none;width:auto;align-self:center;}
138
+ }
92
139
  }