@cahyo-dimas/freeday 1.53.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +657 -535
  2. package/COMPONENTS.md +153 -135
  3. package/README.id.md +46 -45
  4. package/README.md +46 -43
  5. package/USAGE.md +39 -35
  6. package/adapters/blazor/FdyAppShell.razor +50 -0
  7. package/adapters/blazor/FdyAppShell.razor.cs +114 -0
  8. package/adapters/blazor/FdyCfl.razor.cs +1 -1
  9. package/adapters/blazor/FdyChart.razor +1 -1
  10. package/adapters/blazor/FdyChart.razor.cs +3 -3
  11. package/adapters/blazor/FdyChartSeries.cs +1 -1
  12. package/adapters/blazor/FdyCombo.razor.cs +2 -2
  13. package/adapters/blazor/FdyModal.razor +1 -1
  14. package/adapters/blazor/FdyTable.razor.cs +7 -7
  15. package/adapters/blazor/FdyTableFilter.razor +1 -1
  16. package/adapters/blazor/FdyTableFooter.razor +1 -1
  17. package/adapters/blazor/FdyTableFooter.razor.cs +4 -4
  18. package/adapters/blazor/Freeday.Blazor.csproj +1 -1
  19. package/adapters/blazor/FreedayComponentBase.cs +1 -1
  20. package/adapters/blazor/TableModel.cs +3 -3
  21. package/adapters/blazor/TableTypes.cs +3 -3
  22. package/adapters/blazor/freeday-blazor.js +5 -5
  23. package/adapters/core/app-shell.d.ts +30 -0
  24. package/adapters/core/app-shell.js +134 -0
  25. package/adapters/core/table-model.d.ts +2 -2
  26. package/adapters/core/table-model.js +8 -8
  27. package/adapters/react/components/FdyAppShell.tsx +159 -0
  28. package/adapters/react/components/FdyAutocomplete.tsx +1 -1
  29. package/adapters/react/components/FdyCascade.tsx +1 -1
  30. package/adapters/react/components/FdyCfl.tsx +11 -11
  31. package/adapters/react/components/FdyDateRange.tsx +2 -2
  32. package/adapters/react/components/FdyDatepicker.tsx +5 -5
  33. package/adapters/react/components/FdyDrawer.tsx +1 -1
  34. package/adapters/react/components/FdyModal.tsx +2 -2
  35. package/adapters/react/components/FdyTable.tsx +7 -7
  36. package/adapters/react/components/FdyTableFilter.tsx +1 -1
  37. package/adapters/react/components/FdyTableFooter.tsx +5 -5
  38. package/adapters/react/index.d.ts +2 -1
  39. package/adapters/react/index.js +1 -0
  40. package/adapters/react/useFreeday.js +3 -3
  41. package/adapters/vue/components/FdyAppShell.vue +160 -0
  42. package/adapters/vue/components/FdyAutocomplete.vue +1 -1
  43. package/adapters/vue/components/FdyCascade.vue +1 -1
  44. package/adapters/vue/components/FdyCfl.vue +8 -8
  45. package/adapters/vue/components/FdyChart.vue +2 -2
  46. package/adapters/vue/components/FdyCombo.vue +1 -1
  47. package/adapters/vue/components/FdyDateRange.vue +2 -2
  48. package/adapters/vue/components/FdyDatepicker.vue +5 -5
  49. package/adapters/vue/components/FdyDrawer.vue +3 -3
  50. package/adapters/vue/components/FdyModal.vue +3 -3
  51. package/adapters/vue/components/FdyTable.vue +7 -7
  52. package/adapters/vue/components/FdyTableFilter.vue +1 -1
  53. package/adapters/vue/components/FdyTableFooter.vue +5 -5
  54. package/adapters/vue/index.d.ts +3 -2
  55. package/adapters/vue/index.js +1 -0
  56. package/adapters/vue/useFreeday.js +3 -3
  57. package/dist/asset.d.ts +3 -3
  58. package/dist/freeday-app-shell.js +49 -9
  59. package/dist/freeday-autocomplete.js +2 -2
  60. package/dist/freeday-breakpoint.js +1 -1
  61. package/dist/freeday-carousel.js +5 -5
  62. package/dist/freeday-cascade.js +7 -7
  63. package/dist/freeday-cfl.js +9 -9
  64. package/dist/freeday-chart.js +11 -11
  65. package/dist/freeday-chip.js +3 -3
  66. package/dist/freeday-datepicker.js +11 -7
  67. package/dist/freeday-datetime.js +5 -5
  68. package/dist/freeday-drawer.js +1 -1
  69. package/dist/freeday-form.js +14 -14
  70. package/dist/freeday-mask.js +6 -6
  71. package/dist/freeday-menu.js +1 -1
  72. package/dist/freeday-number.js +3 -3
  73. package/dist/freeday-popover.js +3 -3
  74. package/dist/freeday-rating.js +2 -2
  75. package/dist/freeday-select.js +5 -5
  76. package/dist/freeday-slider.js +2 -2
  77. package/dist/freeday-stepper.js +7 -7
  78. package/dist/freeday-table.js +15 -15
  79. package/dist/freeday-tabs.js +1 -1
  80. package/dist/freeday-timepicker.js +2 -2
  81. package/dist/freeday-toast.js +8 -8
  82. package/dist/freeday-tree.js +3 -3
  83. package/dist/freeday-upload.js +25 -25
  84. package/dist/freeday.bundle.css +176 -176
  85. package/dist/freeday.css +170 -170
  86. package/dist/freeday.d.ts +2 -2
  87. package/dist/freeday.js +202 -158
  88. package/dist/freeday.tokens.css +5 -5
  89. package/docs/agent-onboarding.md +42 -42
  90. package/docs/getting-started.md +35 -35
  91. package/docs/integrations.md +18 -18
  92. package/docs/reference-screen.html +14 -14
  93. package/package.json +5 -4
  94. package/src/base.css +6 -6
  95. package/src/components/accordion.css +4 -4
  96. package/src/components/alert.css +1 -1
  97. package/src/components/app-shell.css +10 -10
  98. package/src/components/appbar.css +3 -3
  99. package/src/components/autocomplete.css +1 -1
  100. package/src/components/avatar.css +3 -3
  101. package/src/components/badge.css +5 -5
  102. package/src/components/breadcrumb.css +1 -1
  103. package/src/components/breakpoints.css +4 -4
  104. package/src/components/button.css +13 -13
  105. package/src/components/card.css +8 -8
  106. package/src/components/carousel.css +2 -2
  107. package/src/components/cascade.css +2 -2
  108. package/src/components/cfl.css +4 -4
  109. package/src/components/chart.css +3 -3
  110. package/src/components/chip.css +2 -2
  111. package/src/components/combo.css +3 -3
  112. package/src/components/composition.css +7 -7
  113. package/src/components/datepicker.css +6 -6
  114. package/src/components/datetimepicker.css +1 -1
  115. package/src/components/description-list.css +1 -1
  116. package/src/components/divider.css +1 -1
  117. package/src/components/drawer.css +1 -1
  118. package/src/components/file-upload.css +5 -5
  119. package/src/components/filterbar.css +7 -7
  120. package/src/components/form-grid.css +2 -2
  121. package/src/components/input-group.css +6 -6
  122. package/src/components/input.css +6 -6
  123. package/src/components/kbd.css +1 -1
  124. package/src/components/list.css +6 -6
  125. package/src/components/menu.css +3 -3
  126. package/src/components/modal.css +1 -1
  127. package/src/components/pagination.css +1 -1
  128. package/src/components/progress.css +1 -1
  129. package/src/components/rating.css +2 -2
  130. package/src/components/selection.css +4 -4
  131. package/src/components/skeleton.css +1 -1
  132. package/src/components/slider.css +1 -1
  133. package/src/components/spinner.css +1 -1
  134. package/src/components/states.css +1 -1
  135. package/src/components/stepper.css +2 -2
  136. package/src/components/table.css +15 -15
  137. package/src/components/tabs.css +3 -3
  138. package/src/components/timeline.css +1 -1
  139. package/src/components/timepicker.css +1 -1
  140. package/src/components/toast.css +3 -3
  141. package/src/components/tooltip.css +1 -1
  142. package/src/components/tree.css +2 -2
  143. package/tokens/breakpoints.d.ts +2 -2
  144. package/tokens/breakpoints.mjs +3 -3
  145. package/tokens/tokens.json +1 -1
@@ -1,5 +1,5 @@
1
- /* Freeday tokens + components in one file. GENERATED by tokens/build.mjs. */
2
- /* Freeday tokens GENERATED by tokens/build.mjs. Do not edit by hand. */
1
+ /* Freeday, tokens + components in one file. GENERATED by tokens/build.mjs. */
2
+ /* Freeday tokens. GENERATED by tokens/build.mjs. Do not edit by hand. */
3
3
  :root {
4
4
  --azure-50: #eff4ff;
5
5
  --azure-100: #dbe6fe;
@@ -179,7 +179,7 @@
179
179
  --focus-ring-width: 2px;
180
180
  }
181
181
  /* The SYSTEM default stays root-scoped: it is a statement about the document, and dropping :root
182
- * here would match every element that is not itself [data-theme="light"] which would re-darken
182
+ * here would match every element that is not itself [data-theme="light"], which would re-darken
183
183
  * the children of a light panel, since they carry no attribute of their own. */
184
184
  @media (prefers-color-scheme: dark) {
185
185
  :root:not([data-theme="light"]) {
@@ -241,7 +241,7 @@
241
241
  }
242
242
  /* The two EXPLICIT opt-ins are deliberately NOT scoped to :root, for the same reason as density
243
243
  * below: these are inheriting custom properties, so data-theme on any ancestor re-themes just that
244
- * subtree a dark brand panel beside a light form is an ordinary layout, and it should not require
244
+ * subtree, a dark brand panel beside a light form is an ordinary layout, and it should not require
245
245
  * re-colouring each component by hand. The root still matches, so data-theme on <html> is unchanged.
246
246
  * Both keep the same specificity (0,1,0) as :root and come after it, so they still win there; and
247
247
  * --light after --dark means a light island inside a dark region wins in turn. */
@@ -357,7 +357,7 @@
357
357
  --chart-tick: var(--slate-400);
358
358
  --focus-ring: var(--azure-600);
359
359
  }
360
- /* Density is deliberately NOT scoped to :root. These are custom properties, so they inherit
360
+ /* Density is deliberately NOT scoped to :root. These are custom properties, so they inherit,
361
361
  * putting the attribute on any ancestor (a route wrapper, one section) densifies just that
362
362
  * subtree, which is how density is actually decided: per screen, not per app. The root still
363
363
  * matches, so setting data-density on the html element keeps working exactly as before. */
@@ -369,7 +369,7 @@
369
369
  --control-h: 2rem;
370
370
  }
371
371
  /* ...and the way back out. Inheritance only runs downhill: once the root is compact every subtree is
372
- * compact, and before this rule existed a data-density="comfortable" wrapper matched nothing so the
372
+ * compact, and before this rule existed a data-density="comfortable" wrapper matched nothing, so the
373
373
  * comment above was true in one direction only. An app that is dense overall and carries one region
374
374
  * of shared chrome (a header, a toolbar, a footer shared with a sibling product) had to restate the
375
375
  * defaults by hand, which is exactly the local copy that goes stale when the kit retunes them. */
@@ -381,8 +381,8 @@
381
381
  --control-h: 2.5rem;
382
382
  }
383
383
 
384
- /* Freeday components GENERATED by tokens/build.mjs. Do not edit by hand. */
385
- /* Freeday base layer reset ringan + default via token */
384
+ /* Freeday components. GENERATED by tokens/build.mjs. Do not edit by hand. */
385
+ /* Freeday base layer: reset ringan + default via token */
386
386
  *, *::before, *::after { box-sizing: border-box; }
387
387
  html { -webkit-text-size-adjust: 100%; }
388
388
  body {
@@ -400,21 +400,21 @@ a { color: var(--color-primary); }
400
400
  :where(button, input, select, textarea) { font: inherit; }
401
401
  /* Hidden from sight, kept for assistive tech. `clip` hides PAINTING, not LAYOUT: the box still
402
402
  * has a position, and because it is position:absolute its containing block is the nearest
403
- * POSITIONED ancestor with none, the initial containing block (the document itself).
403
+ * POSITIONED ancestor, and with none, the initial containing block (the document itself).
404
404
  *
405
405
  * That matters, because `overflow` only clips a descendant whose containing block is inside the
406
406
  * overflow box. So a hidden span in a horizontally scrolling table (the kit's own recommended way
407
- * to name an icon button) parks at its static position possibly thousands of px to the right —
407
+ * to name an icon button) parks at its static position, possibly thousands of px to the right —
408
408
  * and drags the whole DOCUMENT sideways: invisible, and immune to `overflow-x:hidden` on every
409
409
  * wrapper. Measured before the fix: 1351px of phantom page scroll from 11 spans.
410
410
  *
411
- * The fix is not here it is on the containers: every clipping/scrolling container in the kit
411
+ * The fix is not here but on the containers: every clipping/scrolling container in the kit
412
412
  * that holds consumer markup declares `position:relative` so it becomes the containing block for
413
413
  * its own out-of-flow content (see test/css.test.mjs, which will not let a new one skip it).
414
- * Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not
414
+ * Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not,
415
415
  * the honest check is `window.scrollTo(9999, 0)` then reading `window.scrollX`. */
416
416
  .fdy-visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
417
- /* Opt-in list reset. base.css is intentionally a *light* reset it does NOT strip list/paragraph
417
+ /* Opt-in list reset. base.css is intentionally a *light* reset: it does NOT strip list/paragraph
418
418
  * margins (see the kit's own list components, which each reset themselves). If you run a utility
419
419
  * framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
420
420
  * bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
@@ -423,10 +423,10 @@ a { color: var(--color-primary); }
423
423
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
424
424
  }
425
425
 
426
- /* Freeday Accordion (native <details>/<summary>, zero-JS) */
427
- /* position:relative containing block for out-of-flow panel content (see base.css). NOT redundant
426
+ /* Freeday. Accordion (native <details>/<summary>, zero-JS) */
427
+ /* position:relative, containing block for out-of-flow panel content (see base.css). NOT redundant
428
428
  with the panel's reveal animation: that animation gives the panel a transform (which happens to
429
- make it a containing block too), but it lives behind prefers-reduced-motion:no-preference so
429
+ make it a containing block too), but it lives behind prefers-reduced-motion:no-preference, so
430
430
  without this line the escape bug appears ONLY for readers who asked for reduced motion. */
431
431
  .fdy-accordion{position:relative;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);}
432
432
  .fdy-accordion__item + .fdy-accordion__item{border-top:var(--bw) solid var(--color-border);}
@@ -438,10 +438,10 @@ a { color: var(--color-primary); }
438
438
  .fdy-accordion__item > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
439
439
  .fdy-accordion__panel{padding:0 var(--space-4) var(--space-4);color:var(--color-text-muted);font-size:var(--text-sm);line-height:var(--leading-normal);}
440
440
  /* Content reveal on open (fade + rise). Native <details> can't animate its own height cross-
441
- * browser without JS, so we animate the revealed panel instead cheap, safe, reduced-motion gated. */
441
+ * browser without JS, so we animate the revealed panel instead, cheap, safe, reduced-motion gated. */
442
442
  @media (prefers-reduced-motion:no-preference){.fdy-accordion__item[open] > .fdy-accordion__panel{animation:fdy-combo-in var(--dur-base) var(--ease-standard);}}
443
443
 
444
- /* Freeday Alert (inline) */
444
+ /* Freeday. Alert (inline) */
445
445
  .fdy-alert{display:flex;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);border:var(--bw) solid var(--color-border);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface-2);}
446
446
  .fdy-alert__icon{flex:none;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-top:.05rem;}
447
447
  .fdy-alert__icon svg{width:100%;height:100%;}
@@ -460,17 +460,17 @@ a { color: var(--color-primary); }
460
460
  .fdy-alert--danger{background:var(--color-danger-soft);border-color:color-mix(in srgb,var(--color-danger) 26%,transparent);}
461
461
  .fdy-alert--danger .fdy-alert__title,.fdy-alert--danger .fdy-alert__icon{color:var(--color-danger-strong);}
462
462
 
463
- /* Freeday App Shell.
463
+ /* Freeday. App Shell.
464
464
  * Natural document scroll: the shell grows with content and the PAGE scrolls,
465
465
  * while the sidebar and topbar are position:sticky so they stay in view. No
466
466
  * fixed-height / internal-scroll trickery (that leaves a white gap if the host
467
467
  * page can still scroll). Structure: .fdy-app > (.fdy-app__sidebar, .fdy-app__content
468
468
  * > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
469
- * topbar a tall containing block so it can travel a bare grid cell can't. */
469
+ * topbar a tall containing block so it can travel, a bare grid cell can't. */
470
470
  .fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
471
471
  .fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;overflow:hidden;flex:none;width:15.5rem;box-sizing:border-box;background:var(--color-surface);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
472
472
  .fdy-app__content{flex:1;min-width:0;display:flex;flex-direction:column;}
473
- /* Brand is a fixed header same height as the topbar so they align, and only
473
+ /* Brand is a fixed header, same height as the topbar so they align, and only
474
474
  * the nav below it scrolls. Bare text (no children) renders as one bold
475
475
  * display line via the rules on .fdy-app__brand itself. For a mark + two-line
476
476
  * title/subtitle, add structured children mirroring .fdy-appbar__brand:
@@ -486,10 +486,10 @@ a { color: var(--color-primary); }
486
486
  .fdy-app__brand-title{display:block;font-family:var(--font-display);font-weight:var(--weight-bold);font-size:var(--text-lg);color:var(--color-text);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
487
487
  .fdy-app__brand-subtitle{display:block;font-size:var(--text-xs);font-weight:var(--weight-regular);color:var(--color-text-muted);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
488
488
  .fdy-nav{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;gap:2px;padding:var(--space-3) var(--space-3) var(--space-6);}
489
- /* Horizontal variant the SAME nav links laid out as a row, for a top-nav application (one that
489
+ /* Horizontal variant, the SAME nav links laid out as a row, for a top-nav application (one that
490
490
  * puts its primary navigation in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar). Deliberately
491
491
  * a modifier and not a new block: the item, its states and `aria-current="page"` are unchanged. That
492
- * is the point a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
492
+ * is the point, a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
493
493
  * ARIA on an anchor) and never a tab role, so borrowing `.fdy-tabs` for routes half-adopts a contract
494
494
  * (roving tabindex, a panel per tab) that route links do not honour. position:relative because the
495
495
  * row scrolls; see base.css on why a scroller must be a containing block. */
@@ -501,7 +501,7 @@ a { color: var(--color-primary); }
501
501
  .fdy-nav__item[aria-current="page"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
502
502
  .fdy-nav__item:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
503
503
  /* Sidebar-menu extras: per-node icon (leading), count badge (trailing), and a
504
- * text label that truncates. Any of these are optional a bare text item works. */
504
+ * text label that truncates. Any of these are optional, a bare text item works. */
505
505
  .fdy-nav__icon{flex:none;display:inline-flex;width:1.15rem;height:1.15rem;}
506
506
  .fdy-nav__icon svg{display:block;width:100%;height:100%;}
507
507
  .fdy-nav__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
@@ -511,7 +511,7 @@ a { color: var(--color-primary); }
511
511
  .fdy-nav__group{margin:0;}
512
512
  .fdy-nav__group + .fdy-nav__group,.fdy-nav__item + .fdy-nav__group{border-top:var(--bw) solid var(--color-border-muted);margin-top:var(--space-1);padding-top:var(--space-1);}
513
513
  /* Prose, and a control name: this is the <summary> of a nav group, at --text-xs uppercase. It is
514
- the second class found spending the decorative ink on type (after .fdy-eyebrow) and it was
514
+ the second class found spending the decorative ink on type (after .fdy-eyebrow), and it was
515
515
  already brightening to --color-text-muted on hover, which is the rule admitting its resting
516
516
  state was too faint to read. */
517
517
  .fdy-nav__grouplabel{list-style:none;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);padding:var(--space-3) var(--space-3) var(--space-1);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:var(--tracking-wide);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
@@ -520,7 +520,7 @@ a { color: var(--color-primary); }
520
520
  .fdy-nav__grouplabel:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
521
521
  .fdy-nav__grouplabel::after{content:"";flex:none;width:.45rem;height:.45rem;margin-right:var(--space-1);border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform var(--dur-fast) var(--ease-standard);opacity:.7;}
522
522
  .fdy-nav__group[open] > .fdy-nav__grouplabel::after{transform:rotate(45deg);}
523
- /* --flat: calmer, non-collapsible grouped nav hide the caret, drop the
523
+ /* --flat: calmer, non-collapsible grouped nav, hide the caret, drop the
524
524
  * inter-group divider and the collapse cursor. Works on native <details> groups
525
525
  * (caret just hidden) and on static <div>/<p> groups (never collapsible). */
526
526
  .fdy-nav--flat .fdy-nav__grouplabel::after{display:none;}
@@ -533,7 +533,7 @@ a { color: var(--color-primary); }
533
533
  .fdy-nav__tree > summary .fdy-nav__caret{flex:none;margin-left:auto;width:.4rem;height:.4rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform var(--dur-fast) var(--ease-standard);opacity:.6;}
534
534
  .fdy-nav__tree[open] > summary .fdy-nav__caret{transform:rotate(45deg);}
535
535
  .fdy-nav__sub{display:flex;flex-direction:column;gap:2px;padding-left:calc(var(--space-3) + 1.15rem);margin-top:2px;}
536
- /* Collapse toggle (hamburger) sits in the topbar; collapses the sidebar. */
536
+ /* Collapse toggle (hamburger), sits in the topbar; collapses the sidebar. */
537
537
  .fdy-app__navtoggle{display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;flex:none;padding:0;border:0;border-radius:var(--radius-md);background:transparent;color:var(--color-text-muted);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
538
538
  .fdy-app__navtoggle:hover{background:var(--color-surface-2);color:var(--color-text);}
539
539
  .fdy-app__navtoggle:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
@@ -546,7 +546,7 @@ a { color: var(--color-primary); }
546
546
  .fdy-app__main{flex:1;padding:var(--space-8);}
547
547
  .fdy-skip{position:absolute;left:var(--space-2);top:var(--space-2);background:var(--color-primary);color:var(--color-on-primary);padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);z-index:100;transform:translateY(-250%);transition:transform var(--dur-fast) var(--ease-standard);}
548
548
  .fdy-skip:focus{transform:translateY(0);}
549
- /* --static: for a bounded/embedded shell (a demo box, not the whole page)
549
+ /* --static: for a bounded/embedded shell (a demo box, not the whole page),
550
550
  * drop sticky so nothing escapes the box; sidebar stretches to fill it. */
551
551
  .fdy-app--static .fdy-app__sidebar{position:static;height:auto;align-self:stretch;}
552
552
  .fdy-app--static .fdy-app__topbar{position:static;}
@@ -562,7 +562,7 @@ a { color: var(--color-primary); }
562
562
  }
563
563
  @media (prefers-reduced-motion:reduce){.fdy-app__sidebar{transition:none;}}
564
564
 
565
- /* Freeday App bar (standalone top bar: brand · actions · spacer).
565
+ /* Freeday. App bar (standalone top bar: brand · actions · spacer).
566
566
  * Distinct from .fdy-app__topbar (which is bound to the shell grid). Use anywhere;
567
567
  * sticky / elevated / dense / coloured via modifiers. Pure CSS. */
568
568
  .fdy-appbar{display:flex;align-items:center;gap:var(--space-3);height:var(--space-16);padding:0 var(--space-4);background:var(--color-surface);color:var(--color-text);border-bottom:var(--bw) solid var(--color-border);}
@@ -575,19 +575,19 @@ a { color: var(--color-primary); }
575
575
  .fdy-appbar__spacer{flex:1;}
576
576
  .fdy-appbar__actions{display:inline-flex;align-items:center;gap:var(--space-1);}
577
577
  /* Primary navigation inside the bar: `<nav class="fdy-nav fdy-nav--horizontal">` with
578
- `.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own a nav link
578
+ `.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own, a nav link
579
579
  is the same component whether the nav is a sidebar column or a top row. */
580
580
  /* Controls on a coloured app bar go on-colour (icons stay legible, hover is a light wash) */
581
581
  .fdy-appbar--primary .fdy-btn--ghost{background:transparent;border-color:transparent;color:var(--color-on-primary);box-shadow:none;}
582
582
  .fdy-appbar--primary .fdy-btn--ghost:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);transform:none;}
583
583
  /* Nav links on a coloured bar: full on-colour ink in every state (a dimmed variant would trade away
584
584
  the contrast the bar's own token pair guarantees). The current page is carried by the background
585
- wash plus the semibold weight the base rule already applies and by `aria-current` itself. */
585
+ wash plus the semibold weight the base rule already applies, and by `aria-current` itself. */
586
586
  .fdy-appbar--primary .fdy-nav__item{color:var(--color-on-primary);}
587
587
  .fdy-appbar--primary .fdy-nav__item:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);}
588
588
  .fdy-appbar--primary .fdy-nav__item[aria-current="page"]{background:color-mix(in srgb,var(--color-on-primary) 24%,transparent);color:var(--color-on-primary);}
589
589
 
590
- /* Freeday Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
590
+ /* Freeday. Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
591
591
  * Enhanced by freeday-autocomplete.js. */
592
592
  .fdy-autocomplete{position:relative;display:block;max-width:22rem;}
593
593
  .fdy-autocomplete__listbox{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:120;min-width:100%;max-height:16rem;overflow-y:auto;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
@@ -600,20 +600,20 @@ a { color: var(--color-primary); }
600
600
  .fdy-autocomplete__empty[hidden]{display:none;}
601
601
  @media (prefers-reduced-motion:no-preference){.fdy-autocomplete__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
602
602
 
603
- /* Freeday Avatar */
603
+ /* Freeday. Avatar */
604
604
  .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;}
605
605
  .fdy-avatar img{width:100%;height:100%;object-fit:cover;}
606
606
  /* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
607
607
  `--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
608
608
  crosses the circle. 1.5rem leaves the same ~4px breathing room the kit's controls give their
609
- icons. Uses `--text-xs` (the smallest type token the kit has there is no `2xs`). */
609
+ icons. Uses `--text-xs` (the smallest type token the kit has, there is no `2xs`). */
610
610
  .fdy-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--text-xs);}
611
611
  .fdy-avatar--sm{width:2rem;height:2rem;font-size:var(--text-xs);}
612
612
  .fdy-avatar--lg{width:3.5rem;height:3.5rem;font-size:var(--text-lg);}
613
613
  .fdy-avatar-group{display:inline-flex;}
614
614
  .fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
615
615
  .fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
616
- /* Decorative identity tones a soft tint of a categorical chart colour with a theme-aware,
616
+ /* Decorative identity tones, a soft tint of a categorical chart colour with a theme-aware,
617
617
  * text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
618
618
  * test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
619
619
  * hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
@@ -627,7 +627,7 @@ a { color: var(--color-primary); }
627
627
  .fdy-avatar--tone-7{--_fdy-avatar-tone:var(--tone-7);}
628
628
  .fdy-avatar--tone-8{--_fdy-avatar-tone:var(--tone-8);}
629
629
 
630
- /* Freeday Badge */
630
+ /* Freeday. Badge */
631
631
  .fdy-badge{display:inline-flex;align-items:center;gap:var(--space-1);font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);line-height:1;white-space:nowrap;padding:.35em .7em;border-radius:var(--radius-full);color:var(--color-primary-strong);background:var(--color-primary-soft);border:var(--bw) solid color-mix(in srgb,var(--color-primary) 22%,transparent);box-shadow:0 1px 1.5px rgba(16,14,30,.05);}
632
632
  .fdy-badge--success{color:var(--color-success-strong);background:var(--color-success-soft);border-color:color-mix(in srgb,var(--color-success) 26%,transparent);}
633
633
  .fdy-badge--warning{color:var(--color-warning-strong);background:var(--color-warning-soft);border-color:color-mix(in srgb,var(--color-warning) 26%,transparent);}
@@ -635,12 +635,12 @@ a { color: var(--color-primary); }
635
635
  .fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
636
636
  .fdy-badge--outline{background:transparent;border-color:var(--color-border-strong);color:var(--color-text-muted);box-shadow:none;}
637
637
 
638
- /* Categorical tone badges a status vocabulary larger than the semantic palette, from the
638
+ /* Categorical tone badges, a status vocabulary larger than the semantic palette, from the
639
639
  * general --tone-1..8 tokens. Same validated AA formula as .fdy-avatar--tone-* and
640
640
  * .fdy-chip--tone-* (text >=4.5:1 both themes, gated by test/contrast.test.mjs).
641
641
  *
642
642
  * Semantics come first: a state that IS good, bad or waiting takes --success / --danger /
643
- * --warning. These are for the rest the states a workflow distinguishes but a palette of five
643
+ * --warning. These are for the rest, the states a workflow distinguishes but a palette of five
644
644
  * cannot. A back-office document list carrying Draft, Submitted, Approved, Completed, Settled,
645
645
  * Closed, Transferred, InDeclaration, Open and Rejected in ONE column collapsed to three colours,
646
646
  * which is the report this shipped for. */
@@ -654,7 +654,7 @@ a { color: var(--color-primary); }
654
654
  .fdy-badge--tone-7{--_fdy-badge-tone:var(--tone-7);}
655
655
  .fdy-badge--tone-8{--_fdy-badge-tone:var(--tone-8);}
656
656
 
657
- /* Overlay badge anchors a small count/dot/icon badge to a corner of any element
657
+ /* Overlay badge, anchors a small count/dot/icon badge to a corner of any element
658
658
  * (icon, button, text). Wrap the content in .fdy-badge-wrap; add .fdy-badge-ov as a
659
659
  * sibling. Default is a solid neutral pill; colour + dot + position via modifiers. */
660
660
  .fdy-badge-wrap{position:relative;display:inline-flex;vertical-align:middle;}
@@ -664,7 +664,7 @@ a { color: var(--color-primary); }
664
664
  /* Solid colours safe for count/text (on-* tokens, AA in light & dark) */
665
665
  .fdy-badge-ov--primary{background:var(--color-primary);color:var(--color-on-primary);}
666
666
  .fdy-badge-ov--danger{background:var(--color-danger-btn);color:var(--color-on-danger);}
667
- /* Dot-oriented accents (for dots plain text on these is not guaranteed AA) */
667
+ /* Dot-oriented accents (for dots, plain text on these is not guaranteed AA) */
668
668
  .fdy-badge-ov--accent{background:var(--color-accent);}
669
669
  .fdy-badge-ov--info{background:var(--color-info);}
670
670
  .fdy-badge-ov--success{background:var(--color-success);}
@@ -674,7 +674,7 @@ a { color: var(--color-primary); }
674
674
  .fdy-badge-ov--bottom-right{top:auto;bottom:0;transform:translate(50%,50%);}
675
675
  .fdy-badge-ov--bottom-left{top:auto;bottom:0;right:auto;left:0;transform:translate(-50%,50%);}
676
676
 
677
- /* Freeday Breadcrumb */
677
+ /* Freeday. Breadcrumb */
678
678
  .fdy-breadcrumb{font-size:var(--text-sm);}
679
679
  .fdy-breadcrumb__list{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);list-style:none;margin:0;padding:0;}
680
680
  .fdy-breadcrumb__item{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--color-text-muted);}
@@ -684,23 +684,23 @@ a { color: var(--color-primary); }
684
684
  .fdy-breadcrumb__item[aria-current="page"]{color:var(--color-text);font-weight:var(--weight-medium);}
685
685
  .fdy-breadcrumb__sep{color:var(--color-text-subtle);user-select:none;}
686
686
 
687
- /* Freeday Responsive display utilities (pairs with freeday-breakpoint.js).
687
+ /* Freeday. Responsive display utilities (pairs with freeday-breakpoint.js).
688
688
  * Breakpoint scale (min-width, px): sm 600 · md 960 · lg 1280 · xl 1920.
689
- * NOTE: media-query breakpoints must be literal CSS can't read custom properties
689
+ * NOTE: media-query breakpoints must be literal. CSS can't read custom properties
690
690
  * inside @media, so these px are structural, not stray magic numbers. */
691
691
  .fdy-hidden{display:none !important;}
692
692
 
693
- /* hide-below-X hidden when the viewport is narrower than X (content shows at X and up) */
693
+ /* hide-below-X, hidden when the viewport is narrower than X (content shows at X and up) */
694
694
  @media (max-width:599.98px){.fdy-hide-below-sm{display:none !important;}}
695
695
  @media (max-width:959.98px){.fdy-hide-below-md{display:none !important;}}
696
696
  @media (max-width:1279.98px){.fdy-hide-below-lg{display:none !important;}}
697
697
 
698
- /* hide-above-X hidden when the viewport is X and wider (content shows below X) */
698
+ /* hide-above-X, hidden when the viewport is X and wider (content shows below X) */
699
699
  @media (min-width:600px){.fdy-hide-above-sm{display:none !important;}}
700
700
  @media (min-width:960px){.fdy-hide-above-md{display:none !important;}}
701
701
  @media (min-width:1280px){.fdy-hide-above-lg{display:none !important;}}
702
702
 
703
- /* Freeday Button */
703
+ /* Freeday. Button */
704
704
  .fdy-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
705
705
  height:var(--control-h);padding:0 var(--space-4);
706
706
  font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-semibold);
@@ -713,7 +713,7 @@ a { color: var(--color-primary); }
713
713
  .fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px color-mix(in srgb,var(--color-primary) 46%,transparent);}
714
714
  .fdy-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none;}
715
715
  /* Pressed / toggle. `aria-pressed` is already the right attribute for a toggle button, so this adds
716
- no class and no new markup contract and it is what makes .fdy-btn-group a complete segmented
716
+ no class and no new markup contract, and it is what makes .fdy-btn-group a complete segmented
717
717
  control: the group joins the borders, this says which segment you are on. Each fill variant
718
718
  defines its OWN pressed look below; a single shared rule cannot work, because a gradient is a
719
719
  background-IMAGE and would blank the ghost variant's background-color. Solid: invert the gradient
@@ -725,12 +725,12 @@ a { color: var(--color-primary); }
725
725
  .fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
726
726
  .fdy-btn--ghost:active{transform:translateY(1px);}
727
727
 
728
- /* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger` —
729
- which every reader parses as "quiet destructive" rendered a second SOLID button beside Save.
728
+ /* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger`,
729
+ which every reader parses as "quiet destructive", rendered a second SOLID button beside Save.
730
730
  That is a hierarchy defect, not a cosmetic one: the base class is already the one primary per
731
731
  screen. `.fdy-menu__item--danger` has always modelled the quiet form (red text, not a red block);
732
732
  the pairing rules below bring it to the button. They are more specific than this one and come
733
- after it, so this rule needs no :not() guard of its own one mechanism, not two. */
733
+ after it, so this rule needs no :not() guard of its own, one mechanism, not two. */
734
734
  .fdy-btn--danger{background:linear-gradient(180deg,var(--color-danger-btn),color-mix(in srgb,#000 12%,var(--color-danger-btn)));color:var(--color-on-danger);box-shadow:0 2px 9px -1px color-mix(in srgb,var(--color-danger-btn) 46%,transparent),inset 0 1px 0 rgba(255,255,255,.22);}
735
735
  .fdy-btn--danger:hover{transform:translateY(-1px);box-shadow:0 6px 18px -2px color-mix(in srgb,var(--color-danger-btn) 54%,transparent),inset 0 1px 0 rgba(255,255,255,.22);}
736
736
  .fdy-btn--danger:active{transform:translateY(1px);}
@@ -744,21 +744,21 @@ a { color: var(--color-primary); }
744
744
  .fdy-btn--sm{height:calc(var(--control-h) - var(--space-2));padding:0 var(--space-3);font-size:var(--text-xs);border-radius:var(--radius-sm);}
745
745
  .fdy-btn--lg{height:calc(var(--control-h) + var(--space-2));padding:0 var(--space-6);font-size:var(--text-base);border-radius:var(--radius-lg);}
746
746
 
747
- /* Icon inside a button scales with the button's font size (works for text+icon & icon-only) */
747
+ /* Icon inside a button, scales with the button's font size (works for text+icon & icon-only) */
748
748
  .fdy-btn__icon{display:inline-flex;flex:none;}
749
749
  .fdy-btn__icon svg{display:block;width:1.15em;height:1.15em;}
750
750
 
751
- /* Text button lowest emphasis (no fill/border), colored label, subtle hover tint */
751
+ /* Text button, lowest emphasis (no fill/border), colored label, subtle hover tint */
752
752
  .fdy-btn--text{background:none;color:var(--color-primary-strong);border-color:transparent;box-shadow:none;}
753
753
  .fdy-btn--text:hover{transform:none;background:var(--color-primary-soft);box-shadow:none;}
754
754
  .fdy-btn--text:active{transform:translateY(1px);box-shadow:none;}
755
- /* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink) the same pairing
755
+ /* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink), the same pairing
756
756
  .fdy-nav__item uses for the current page, and one the AA contrast gate already covers. This is
757
757
  the realistic segmented control: a row of --ghost buttons with exactly one aria-pressed. */
758
758
  .fdy-btn--ghost[aria-pressed="true"],.fdy-btn--text[aria-pressed="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);border-color:var(--color-primary-border);box-shadow:none;}
759
759
  .fdy-btn--ghost[aria-pressed="true"]:hover,.fdy-btn--text[aria-pressed="true"]:hover{background:var(--color-primary-soft);transform:none;box-shadow:none;}
760
760
 
761
- /* Stretched hit area (#007) spread this button's click target over its nearest positioned
761
+ /* Stretched hit area (#007), spread this button's click target over its nearest positioned
762
762
  * ancestor, so "one card, one primary action, one escape hatch" needs no click handler on the
763
763
  * <article>: the real control stays a real <button>/<a> and keeps its keyboard semantics.
764
764
  * `.fdy-card` is already position:relative, so it is the anchor.
@@ -767,19 +767,19 @@ a { color: var(--color-primary); }
767
767
  * a transformed element becomes the containing block for its own absolutely-positioned descendants,
768
768
  * so `inset:0` re-anchors from the card to the button's own box the moment the button is hovered or
769
769
  * pressed. Measured: mousedown lands on the button, the overlay collapses under the pointer, mouseup
770
- * lands on some other element, and the browser fires `click` on their common ancestor the button
770
+ * lands on some other element, and the browser fires `click` on their common ancestor, the button
771
771
  * never gets one. It demos fine on hover for --text/--ghost (their hover is already transform:none)
772
772
  * and only a real press reveals it; the default and --danger fills break one step earlier, on hover.
773
773
  * Press feedback moves to the card, which is the object actually being pressed (see card.css). */
774
774
  .fdy-btn--stretch::after{content:'';position:absolute;inset:0;}
775
775
  .fdy-btn--stretch:hover,.fdy-btn--stretch:active{transform:none;}
776
776
 
777
- /* Icon-only square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
777
+ /* Icon-only, square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
778
778
  .fdy-btn--icon{width:var(--control-h);padding:0;}
779
779
  .fdy-btn--icon.fdy-btn--sm{width:calc(var(--control-h) - var(--space-2));}
780
780
  .fdy-btn--icon.fdy-btn--lg{width:calc(var(--control-h) + var(--space-2));}
781
781
 
782
- /* Button group segmented, joined borders, rounded only at the ends */
782
+ /* Button group, segmented, joined borders, rounded only at the ends */
783
783
  .fdy-btn-group{display:inline-flex;}
784
784
  .fdy-btn-group .fdy-btn{border-radius:0;}
785
785
  .fdy-btn-group .fdy-btn:not(:first-child){margin-left:calc(var(--bw) * -1);}
@@ -787,7 +787,7 @@ a { color: var(--color-primary); }
787
787
  .fdy-btn-group .fdy-btn:first-child{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md);}
788
788
  .fdy-btn-group .fdy-btn:last-child{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md);}
789
789
 
790
- /* FAB circular elevated action button (icon), optional extended pill with label */
790
+ /* FAB, circular elevated action button (icon), optional extended pill with label */
791
791
  .fdy-fab{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);width:3.5rem;height:3.5rem;padding:0;border:var(--bw) solid transparent;border-radius:var(--radius-full);cursor:pointer;color:var(--color-on-primary);background:linear-gradient(180deg,var(--color-primary),color-mix(in srgb,#000 10%,var(--color-primary)));box-shadow:var(--shadow-3);transition:transform var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
792
792
  .fdy-fab:hover{transform:translateY(-1px);box-shadow:var(--shadow-4);}
793
793
  .fdy-fab:active{transform:translateY(1px);}
@@ -800,35 +800,35 @@ a { color: var(--color-primary); }
800
800
  .fdy-fab--danger{background:linear-gradient(180deg,var(--color-danger-btn),color-mix(in srgb,#000 12%,var(--color-danger-btn)));color:var(--color-on-danger);}
801
801
  .fdy-fab--accent{background:linear-gradient(180deg,var(--color-accent),color-mix(in srgb,#000 10%,var(--color-accent)));color:var(--color-on-accent);}
802
802
 
803
- /* Freeday Card */
804
- /* position:relative containing block for out-of-flow card content (see base.css); it also gives
803
+ /* Freeday. Card */
804
+ /* position:relative, containing block for out-of-flow card content (see base.css); it also gives
805
805
  consumers the anchor a corner ribbon/badge needs. */
806
806
  .fdy-card{position:relative;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-lift);transition:box-shadow var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
807
807
  .fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
808
808
  .fdy-card--interactive{cursor:pointer;}
809
809
  .fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
810
- /* Pressing a stretched target (see .fdy-btn--stretch) is pressing the CARD the button's own
810
+ /* Pressing a stretched target (see .fdy-btn--stretch) is pressing the CARD, the button's own
811
811
  nudge had to go, and the object under the pointer is the card. Ordered before the reduced-motion
812
812
  block and the disabled rules so both still win at equal specificity. */
813
813
  .fdy-card--interactive:has(.fdy-btn--stretch:active){transform:translateY(-1px);}
814
814
  /* The global reduced-motion reset kills the ANIMATION; drop the -3px shift too so the card
815
815
  * doesn't jump. The shadow still communicates the affordance. */
816
816
  @media (prefers-reduced-motion:reduce){.fdy-card--interactive:hover,.fdy-card--interactive:has(.fdy-btn--stretch:active){transform:none;}}
817
- /* When the whole card IS the control a real <button> so the row is keyboard-reachable and
818
- * announced reset the UA button box. Note: it does NOT reset background/border, because
817
+ /* When the whole card IS the control, a real <button> so the row is keyboard-reachable and
818
+ * announced, reset the UA button box. Note: it does NOT reset background/border, because
819
819
  * .fdy-card already sets its own surface + border (resetting them here would strip the card). */
820
820
  .fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
821
- /* Disabled card-as-control same contract as .fdy-list__row--button: dim + not-allowed, and the
821
+ /* Disabled card-as-control, same contract as .fdy-list__row--button: dim + not-allowed, and the
822
822
  --interactive lift is withdrawn so a refusing control stops answering the pointer. */
823
823
  .fdy-card--button:disabled,.fdy-card--button[aria-disabled="true"],.fdy-card--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
824
824
  .fdy-card--interactive:disabled:hover,.fdy-card--interactive[aria-disabled="true"]:hover{box-shadow:var(--shadow-lift);transform:none;}
825
- /* Escape hatches stay clickable above a stretched target automatic, not a documented convention,
825
+ /* Escape hatches stay clickable above a stretched target, automatic, not a documented convention,
826
826
  because forgetting it fails SILENTLY: the secondary control looks and hovers exactly as before and
827
827
  simply never receives the click, which is the very symptom the pattern exists to cure. Matched by
828
828
  shape (anything focusable) so a control the kit does not ship yet is covered too. */
829
829
  :is(.fdy-card,.fdy-list__row):has(.fdy-btn--stretch) :is(a[href],button,input,select,textarea,[tabindex]):not(.fdy-btn--stretch){z-index:1;}
830
830
  /* The position is a DEFAULT, not an override: z-index needs a positioned box (or a flex/grid item),
831
- but an app that deliberately pins a control inside the card a corner dismiss, a favourite star
831
+ but an app that deliberately pins a control inside the card, a corner dismiss, a favourite star,
832
832
  must keep its own `position:absolute`. Wrapped in :where() so this whole selector weighs nothing
833
833
  and ANY app rule wins it; measured, because at normal specificity the kit silently dragged an
834
834
  absolutely positioned dismiss button back into the flow. Do not merge these two rules. */
@@ -838,9 +838,9 @@ a { color: var(--color-primary); }
838
838
  .fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
839
839
  .fdy-card__footer{padding:var(--space-3) var(--space-5);border-top:var(--bw) solid var(--color-border-muted);background:linear-gradient(180deg,var(--color-surface),var(--color-surface-2));display:flex;gap:var(--space-2);justify-content:flex-end;}
840
840
 
841
- /* Freeday Carousel (scroll-snap track + arrows + dots). Enhanced by freeday-carousel.js. */
841
+ /* Freeday. Carousel (scroll-snap track + arrows + dots). Enhanced by freeday-carousel.js. */
842
842
  .fdy-carousel{position:relative;}
843
- /* position:relative the arrows are positioned against .fdy-carousel (the parent), so this only
843
+ /* position:relative means the arrows are positioned against .fdy-carousel (the parent), so this only
844
844
  catches out-of-flow content inside the SLIDES, which would otherwise escape the viewport
845
845
  entirely and scroll the page by one slide-offset per slide (see base.css). */
846
846
  .fdy-carousel__viewport{position:relative;display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;border-radius:var(--radius-lg);scrollbar-width:none;-ms-overflow-style:none;}
@@ -859,10 +859,10 @@ a { color: var(--color-primary); }
859
859
  .fdy-carousel__dot:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 40%,transparent);}
860
860
  @media (prefers-reduced-motion:reduce){.fdy-carousel__viewport{scroll-behavior:auto;}}
861
861
 
862
- /* Freeday Cascade select (hierarchical drill-down picker).
862
+ /* Freeday. Cascade select (hierarchical drill-down picker).
863
863
  * Built by freeday-cascade.js from a [data-fdy-cascade] wrapper whose hidden
864
864
  * nested <ul> is the data model: a <li> with a child <ul> is a branch, one
865
- * without is a selectable leaf. The popover shows one level at a time branches
865
+ * without is a selectable leaf. The popover shows one level at a time, branches
866
866
  * drill in, a back control ascends, a leaf selects (value = full path).
867
867
  * Trigger visual matches the combo / date / time control family. */
868
868
  .fdy-cascade{position:relative;display:inline-block;width:100%;max-width:22rem;}
@@ -899,16 +899,16 @@ a { color: var(--color-primary); }
899
899
  .fdy-cascade__opt-arrow{flex:none;width:.45rem;height:.45rem;border-right:2px solid var(--color-text-subtle);border-bottom:2px solid var(--color-text-subtle);transform:rotate(-45deg);}
900
900
  @media (prefers-reduced-motion:no-preference){.fdy-cascade__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
901
901
 
902
- /* Freeday Choose-from-list dialog (searchable master-data picker).
902
+ /* Freeday. Choose-from-list dialog (searchable master-data picker).
903
903
  * Built on .fdy-modal: header + search + scrollable results table + footer.
904
904
  * Backs a choose-from-list field (readonly input + .fdy-input-group__btn magnifier). */
905
905
  .fdy-modal--cfl{width:min(46rem,calc(100vw - var(--space-8)));}
906
906
  .fdy-modal--cfl .fdy-modal__body{padding:0;display:flex;flex-direction:column;min-height:0;overflow:hidden;}
907
907
 
908
- /* Search bar single leading-icon field; Enter/typing filters the results. */
908
+ /* Search bar, single leading-icon field; Enter/typing filters the results. */
909
909
  .fdy-cfl__search{flex:none;padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
910
910
 
911
- /* Results dense rows, sticky header, its own scroll region. */
911
+ /* Results, dense rows, sticky header, its own scroll region. */
912
912
  .fdy-cfl__results{overflow:auto;max-height:min(24rem,50vh);}
913
913
  .fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
914
914
  .fdy-cfl__row{cursor:pointer;}
@@ -923,12 +923,12 @@ a { color: var(--color-primary); }
923
923
  .fdy-cfl__empty{padding:var(--space-8) var(--space-5);text-align:center;color:var(--color-text-muted);font-size:var(--text-sm);margin:0;}
924
924
  .fdy-cfl__empty[hidden]{display:none;}
925
925
 
926
- /* Footer selection count (multi) sits left, actions right. */
926
+ /* Footer, selection count (multi) sits left, actions right. */
927
927
  .fdy-modal--cfl .fdy-modal__footer{justify-content:space-between;}
928
928
  .fdy-cfl__count{font-size:var(--text-sm);color:var(--color-text-muted);}
929
929
  .fdy-cfl__actions{display:flex;gap:var(--space-2);}
930
930
 
931
- /* Freeday Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
931
+ /* Freeday. Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
932
932
  * Colour comes from --fdy-chart (a token, set inline by the renderer from data-fdy-color). */
933
933
 
934
934
  /* Sparkline / trend line (SVG) */
@@ -948,7 +948,7 @@ a { color: var(--color-primary); }
948
948
  /* Hover emphasis: dim all bars while the chart is hovered, keep the hovered one lit. */
949
949
  @media (hover:hover){.fdy-bars:hover .fdy-bars__bar{opacity:.4;}.fdy-bars__col:hover .fdy-bars__bar{opacity:1;}}
950
950
 
951
- /* Interactive tooltip (shared by bar + donut) Chart.js-style hover readout. */
951
+ /* Interactive tooltip (shared by bar + donut). Chart.js-style hover readout. */
952
952
  .fdy-chart__tip{position:absolute;z-index:5;left:0;top:0;pointer-events:none;transform:translate(-50%,calc(-100% - 8px));background:var(--color-inverse-surface);color:var(--color-inverse-text);font-size:var(--text-xs);font-weight:var(--weight-medium);font-variant-numeric:tabular-nums;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);white-space:nowrap;box-shadow:var(--shadow-2);opacity:0;transition:opacity var(--dur-fast) var(--ease-standard);}
953
953
  .fdy-chart__tip.is-visible{opacity:1;}
954
954
 
@@ -969,7 +969,7 @@ a { color: var(--color-primary); }
969
969
  /* Horizontal legend for cartesian charts (legend sits above the plot). */
970
970
  .fdy-chart__legend--row{flex-direction:row;flex-wrap:wrap;gap:var(--space-2) var(--space-4);}
971
971
 
972
- /* Cartesian charts (line / area / multi-series & stacked bar) SVG with themed axes.
972
+ /* Cartesian charts (line / area / multi-series & stacked bar). SVG with themed axes.
973
973
  * Axis colour comes from --chart-grid / --chart-tick (both theme-aware); series colours are
974
974
  * set inline by the renderer from --chart-1..8, so the whole chart recolours on data-theme. */
975
975
  .fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100%;--fdy-chart-tick-size:var(--text-xs);}
@@ -984,10 +984,10 @@ a { color: var(--color-primary); }
984
984
  .fdy-chart-xy__bar{transition:opacity var(--dur-fast) var(--ease-standard);}
985
985
  .fdy-chart-xy__band{fill:transparent;cursor:pointer;}
986
986
 
987
- /* Freeday Chip (tag / removable filter) */
987
+ /* Freeday. Chip (tag / removable filter) */
988
988
  .fdy-chip{display:inline-flex;align-items:center;gap:var(--space-2);height:1.75rem;padding:0 var(--space-3);border-radius:var(--radius-full);font-size:var(--text-xs);font-weight:var(--weight-medium);color:var(--color-text);background:var(--color-surface-3);border:var(--bw) solid var(--color-border);}
989
989
  .fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:color-mix(in srgb,var(--color-primary) 22%,transparent);}
990
- /* Categorical tone chips non-semantic category tags / legend swatches, from the general
990
+ /* Categorical tone chips, non-semantic category tags / legend swatches, from the general
991
991
  * --tone-1..8 palette. Same validated AA formula as .fdy-avatar--tone-* (text >=4.5:1 both themes,
992
992
  * gated by test/contrast.test.mjs). */
993
993
  .fdy-chip--tone-1,.fdy-chip--tone-2,.fdy-chip--tone-3,.fdy-chip--tone-4,.fdy-chip--tone-5,.fdy-chip--tone-6,.fdy-chip--tone-7,.fdy-chip--tone-8{background:color-mix(in srgb,var(--_fdy-chip-tone) 18%,var(--color-surface));color:color-mix(in srgb,var(--_fdy-chip-tone) 50%,var(--color-text));border-color:color-mix(in srgb,var(--_fdy-chip-tone) 30%,var(--color-border));}
@@ -1015,14 +1015,14 @@ a { color: var(--color-primary); }
1015
1015
  .fdy-chip__check svg{display:block;width:100%;height:100%;}
1016
1016
  .fdy-chip--filter[aria-pressed="true"] .fdy-chip__check{display:inline-flex;}
1017
1017
 
1018
- /* Freeday Combobox (custom styled select, WAI-ARIA APG select-only) */
1018
+ /* Freeday. Combobox (custom styled select, WAI-ARIA APG select-only) */
1019
1019
  .fdy-combo{position:relative;display:block;max-width:22rem;}
1020
1020
  .fdy-combo__button{display:inline-flex;align-items:center;justify-content:space-between;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
1021
1021
  .fdy-combo__button:hover{border-color:var(--color-text-muted);}
1022
1022
  .fdy-combo__button:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1023
1023
  .fdy-combo__button.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1024
1024
  .fdy-combo__button:disabled,.fdy-combo__button[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
1025
- /* Read-only: stays focusable & shows its value at full contrast but can't open muted surface,
1025
+ /* Read-only: stays focusable & shows its value at full contrast but can't open, muted surface,
1026
1026
  softened border, default cursor, no focus-ring escalation (matches input[readonly], #39). */
1027
1027
  .fdy-combo__button[aria-readonly="true"]{background:var(--color-surface-2);border-color:var(--color-border);cursor:default;}
1028
1028
  .fdy-combo__button[aria-readonly="true"]:hover{border-color:var(--color-border);}
@@ -1046,7 +1046,7 @@ a { color: var(--color-primary); }
1046
1046
  .fdy-combo__option.is-highlighted[aria-selected="true"]{box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 55%,transparent);}
1047
1047
  /* The tick is CSS, not content (#004 §2). Inside role="option" it joined the option's ACCESSIBLE
1048
1048
  NAME, so the selected option announced "✓August" while every other announced "August": the state
1049
- was read twice once as aria-selected, once as decoration and a test asking for an option by
1049
+ was read twice, once as aria-selected, once as decoration, and a test asking for an option by
1050
1050
  its own label stopped matching the one that was selected. The `/ ""` is alt text: the glyph is
1051
1051
  painted, the accessibility tree gets nothing. Same reasoning as .fdy-label--required (1.29.0).
1052
1052
  The empty box still reserves its width, so labels stay aligned whether selected or not. */
@@ -1055,11 +1055,11 @@ a { color: var(--color-primary); }
1055
1055
  @media (prefers-reduced-motion:no-preference){.fdy-combo__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
1056
1056
  @keyframes fdy-combo-in{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:none;}}
1057
1057
 
1058
- /* Freeday Page composition & type roles.
1058
+ /* Freeday. Page composition & type roles.
1059
1059
  *
1060
1060
  * The kit stops at the component; these primitives encode *how a page is assembled* so screens
1061
1061
  * built by different people cohere. They carry the usage doctrine (see USAGE.md) in markup: a
1062
- * named type role gets used, a doc gets skimmed. Everything here is token-driven layout no new
1062
+ * named type role gets used, a doc gets skimmed. Everything here is token-driven layout, no new
1063
1063
  * colours, no new shadows. Pair with .fdy-app (the shell) and the components inside.
1064
1064
  */
1065
1065
 
@@ -1068,7 +1068,7 @@ a { color: var(--color-primary); }
1068
1068
  * page title (the mistake that collapses three levels into one grey mush). */
1069
1069
  /* --color-text-muted, not -subtle: an eyebrow is a small label naming the thing below it, i.e.
1070
1070
  prose. -subtle is gated at 3.0 on purpose (placeholders, separators, decorative glyphs) and
1071
- measures 4.41 on the page ground axe reports it as a serious color-contrast failure. Same
1071
+ measures 4.41 on the page ground, axe reports it as a serious color-contrast failure. Same
1072
1072
  argument, same ink, same 4.41 as .fdy-stat__label in 1.30.0. */
1073
1073
  .fdy-eyebrow{font-family:var(--font-body);font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-muted);margin:0;}
1074
1074
  .fdy-title-page{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:var(--leading-tight);color:var(--color-text);margin:0;text-wrap:balance;}
@@ -1102,19 +1102,19 @@ a { color: var(--color-primary); }
1102
1102
  .fdy-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:var(--space-5);}
1103
1103
  .fdy-stat{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
1104
1104
  /* --color-text-muted, not -subtle: this is a LABEL, i.e. text, and -subtle is deliberately gated at
1105
- 3.0 (placeholders, dividers, decorative glyphs). On surface-2 -subtle measures 4.41 under AA
1105
+ 3.0 (placeholders, dividers, decorative glyphs). On surface-2 -subtle measures 4.41, under AA
1106
1106
  for text, and a stat label is often the only thing naming the number above it. */
1107
1107
  .fdy-stat__label{font-size:var(--text-xs);font-weight:var(--weight-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--color-text-muted);}
1108
1108
  .fdy-stat__value{font-family:var(--font-display);font-size:var(--text-3xl);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tighter);line-height:1;color:var(--color-text);font-variant-numeric:tabular-nums;}
1109
1109
  .fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
1110
1110
  .fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
1111
- /* A stat value is display type sized for a COUNT "1,284" and a back office puts money in it.
1111
+ /* A stat value is display type sized for a COUNT, "1,284", and a back office puts money in it.
1112
1112
  "IDR 300,000.00" needs 224px at --text-3xl and the grid's own track is 11rem, so it wrapped
1113
1113
  between the currency and the number: two lines of 31px where the design says one. Measured, not
1114
1114
  guessed (#020).
1115
1115
  The value shrinks only when its own column is too narrow to hold it, so a wide dashboard keeps
1116
1116
  exactly the type it has today. Scoped to a stat INSIDE .fdy-stats, where the width comes from the
1117
- grid track `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
1117
+ grid track, `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
1118
1118
  own content. The unconditional rule above stays as the fallback wherever @container is missing. */
1119
1119
  @supports (container-type:inline-size){
1120
1120
  .fdy-stats>.fdy-stat{container-type:inline-size;}
@@ -1127,7 +1127,7 @@ a { color: var(--color-primary); }
1127
1127
  /* Icon box (#001 §4). The kit ships no icons on purpose, but every icon slot it DOES ship
1128
1128
  (.fdy-btn__icon, .fdy-nav__icon, .fdy-state__icon) assumes something sensible inside, so a
1129
1129
  standalone icon had no contract and every app invented one. 1em keeps the glyph matched to the
1130
- text beside it at every scale bring your own paths, use our box. Works on the <svg> itself or
1130
+ text beside it at every scale, bring your own paths, use our box. Works on the <svg> itself or
1131
1131
  on a wrapper around it. */
1132
1132
  .fdy-icon{display:inline-block;width:1em;height:1em;flex:none;vertical-align:-0.125em;}
1133
1133
  .fdy-icon>svg{display:block;width:100%;height:100%;}
@@ -1140,13 +1140,13 @@ a { color: var(--color-primary); }
1140
1140
  .fdy-text-warning{color:var(--color-warning-strong);}
1141
1141
  .fdy-text-danger{color:var(--color-danger-strong);}
1142
1142
 
1143
- /* Freeday Date picker (input-styled trigger + calendar popover).
1143
+ /* Freeday. Date picker (input-styled trigger + calendar popover).
1144
1144
  * Built by freeday-datepicker.js from an empty [data-fdy-datepicker] wrapper. */
1145
1145
  .fdy-datepicker{position:relative;display:inline-block;width:100%;max-width:14rem;}
1146
1146
  /* Inside a labelled field, a picker fills the field like every other control.
1147
1147
  The 14rem/11rem caps above suit a picker standing on its own; in a form grid
1148
- they leave the date box visibly short of the combo beside it 224px against
1149
- 319px in a two-column layout and the row reads as ragged. The field's own
1148
+ they leave the date box visibly short of the combo beside it. 224px against
1149
+ 319px in a two-column layout, and the row reads as ragged. The field's own
1150
1150
  max-width is what should govern there. */
1151
1151
  .fdy-field>.fdy-datepicker,.fdy-field>.fdy-timepicker,.fdy-field>.fdy-daterange{max-width:none;}
1152
1152
  .fdy-datepicker__trigger{display:inline-flex;align-items:center;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
@@ -1192,12 +1192,12 @@ a { color: var(--color-primary); }
1192
1192
  .fdy-cal__nav:hover{background:var(--color-surface-2);color:var(--color-text);}
1193
1193
  .fdy-cal__nav:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1194
1194
  .fdy-cal__grid{display:grid;grid-template-columns:repeat(7,2rem);gap:2px;}
1195
- /* Month grid 3×4 over the same track width as the 7-column day grid, so the panel does not resize
1195
+ /* Month grid. 3×4 over the same track width as the 7-column day grid, so the panel does not resize
1196
1196
  when you drill and the pointer does not have to travel.
1197
1197
 
1198
1198
  AFTER `.fdy-cal__grid`, and that is the whole rule: the element carries both classes, they weigh
1199
1199
  the same, so source order decides. Declared earlier, the modifier lost to the base it modifies and
1200
- twelve months rendered seven across the layout the comment above described, never once applied. */
1200
+ twelve months rendered seven across, the layout the comment above described, never once applied. */
1201
1201
  .fdy-cal__grid--months,.fdy-cal__grid--years{grid-template-columns:repeat(3,1fr);gap:var(--space-1);}
1202
1202
  .fdy-cal__dow{display:flex;align-items:center;justify-content:center;height:1.75rem;font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--color-text-muted);text-transform:capitalize;}
1203
1203
  .fdy-cal__day{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text);font-size:var(--text-sm);font-variant-numeric:tabular-nums;cursor:pointer;}
@@ -1215,14 +1215,14 @@ a { color: var(--color-primary); }
1215
1215
  /* Responsive: each picker may shrink (min-width:0) & wrap so the range never overflows a
1216
1216
  narrow row/phone; inline-flex still hugs to ~23rem at wider widths (look unchanged). */
1217
1217
  .fdy-daterange{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);}
1218
- /* Each picker shrinks from an 11rem basis but not below ~7rem enough for "dd/mm/yyyy" + the icon,
1218
+ /* Each picker shrinks from an 11rem basis but not below ~7rem, enough for "dd/mm/yyyy" + the icon,
1219
1219
  so a squeezed range compresses gracefully (and wraps) instead of clipping the date. */
1220
1220
  .fdy-daterange .fdy-datepicker{flex:0 1 11rem;min-width:7rem;}
1221
1221
  .fdy-daterange__sep{flex:none;color:var(--color-text-subtle);}
1222
1222
 
1223
1223
  @media (prefers-reduced-motion:no-preference){.fdy-datepicker__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
1224
1224
 
1225
- /* Freeday Datetime picker (composed: date picker + time picker → one value).
1225
+ /* Freeday. Datetime picker (composed: date picker + time picker → one value).
1226
1226
  * A thin composer (freeday-datetime.js) links a child [data-fdy-datepicker] and
1227
1227
  * [data-fdy-timepicker] and emits a single combined event. Icon variants come from
1228
1228
  * the child pickers (data-fdy-no-icon / <template data-fdy-icon>). */
@@ -1231,18 +1231,18 @@ a { color: var(--color-primary); }
1231
1231
  .fdy-datetimepicker .fdy-datepicker{width:12rem;max-width:none;}
1232
1232
  .fdy-datetimepicker .fdy-timepicker{width:8.5rem;max-width:none;}
1233
1233
 
1234
- /* Freeday Description list (key-value detail view) */
1234
+ /* Freeday. Description list (key-value detail view) */
1235
1235
  .fdy-dl{display:grid;grid-template-columns:auto 1fr;gap:var(--space-2) var(--space-5);margin:0;}
1236
1236
  .fdy-dl dt{color:var(--color-text-muted);font-size:var(--text-sm);}
1237
1237
  .fdy-dl dd{margin:0;color:var(--color-text);font-size:var(--text-sm);font-weight:var(--weight-medium);}
1238
1238
  .fdy-dl--rows{grid-template-columns:1fr;gap:var(--space-1);}
1239
1239
  .fdy-dl--rows dt{margin-top:var(--space-2);}
1240
1240
 
1241
- /* Freeday Divider */
1241
+ /* Freeday. Divider */
1242
1242
  .fdy-divider{border:0;border-top:var(--bw) solid var(--color-border);margin:var(--space-4) 0;}
1243
1243
  .fdy-divider--vertical{border-top:0;border-left:var(--bw) solid var(--color-border);margin:0 var(--space-4);align-self:stretch;}
1244
1244
 
1245
- /* Freeday Drawer (temporary side panel on native <dialog>: focus-trap / Esc /
1245
+ /* Freeday. Drawer (temporary side panel on native <dialog>: focus-trap / Esc /
1246
1246
  * backdrop / return-focus are free). Anchors left (default) or right.
1247
1247
  * Enhanced by freeday-drawer.js (open trigger + backdrop-click close). */
1248
1248
  /* display is gated behind [open] so the native <dialog> stays hidden until opened.
@@ -1269,7 +1269,7 @@ a { color: var(--color-primary); }
1269
1269
  .fdy-drawer[open]::backdrop{opacity:0;}
1270
1270
  }
1271
1271
 
1272
- /* Freeday File upload (drop target + explicit per-file states).
1272
+ /* Freeday. File upload (drop target + explicit per-file states).
1273
1273
  * Enhanced by freeday-upload.js; states: idle · drag-over · uploading · done · error. */
1274
1274
  .fdy-dropzone{display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:var(--space-6);text-align:center;border:2px dashed var(--color-control-border);border-radius:var(--radius-lg);background:var(--color-surface-2);color:var(--color-text-muted);cursor:pointer;transition:border-color var(--dur-fast) var(--ease-standard),background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
1275
1275
  .fdy-dropzone:hover{border-color:var(--color-text-muted);}
@@ -1281,17 +1281,17 @@ a { color: var(--color-primary); }
1281
1281
  .fdy-dropzone__icon svg{display:block;width:2rem;height:2rem;}
1282
1282
  .fdy-dropzone__title{font-size:inherit;font-weight:var(--weight-medium);color:var(--color-text);margin:0;}
1283
1283
  .fdy-dropzone__hint{font-size:var(--text-xs);}
1284
- /* Wide (horizontal) layout icon beside the copy instead of stacked, for full-width
1284
+ /* Wide (horizontal) layout, icon beside the copy instead of stacked, for full-width
1285
1285
  * sections. Wrap the title + hint in .fdy-dropzone__text. */
1286
1286
  .fdy-dropzone--row{flex-direction:row;align-items:center;justify-content:center;text-align:left;gap:var(--space-4);}
1287
1287
  .fdy-dropzone__text{display:flex;flex-direction:column;gap:var(--space-1);}
1288
1288
 
1289
1289
  /* File rows (list of added/uploaded/rejected files) */
1290
- /* Symmetric on purpose. The list carried margin-top and nothing below, so whatever follows it
1291
- an "Add files" button, in every upload UI there is sat flush against the last row: measured
1290
+ /* Symmetric on purpose. The list carried margin-top and nothing below, so whatever follows it,
1291
+ an "Add files" button, in every upload UI there is, sat flush against the last row: measured
1292
1292
  0px. A block that claims space on one side only is not spacing, it is a lean. */
1293
1293
  .fdy-filelist{display:flex;flex-direction:column;gap:var(--space-2);margin-top:var(--space-3);margin-bottom:var(--space-3);}
1294
- /* Nothing after it, nothing to separate from so the room is not spent. */
1294
+ /* Nothing after it, nothing to separate from, so the room is not spent. */
1295
1295
  .fdy-filelist:last-child{margin-bottom:0;}
1296
1296
  /* Side-by-side rows on wide screens (reflow to one column when narrow). */
1297
1297
  .fdy-filelist--grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));}
@@ -1310,7 +1310,7 @@ a { color: var(--color-primary); }
1310
1310
  .fdy-file--error .fdy-file__icon{color:var(--color-danger);}
1311
1311
  .fdy-file--error .fdy-file__sub{color:var(--color-danger-strong);}
1312
1312
 
1313
- /* Freeday Filter bar (a consistent row of filters: search + selects + actions).
1313
+ /* Freeday. Filter bar (a consistent row of filters: search + selects + actions).
1314
1314
  * Solves the "ragged filter row": apps improvised flex rows with ad-hoc
1315
1315
  * flex/min-width, so grow fields ballooned while selects stayed tiny and no two
1316
1316
  * screens matched. Here every field snaps to a shared width rhythm; one field may
@@ -1319,7 +1319,7 @@ a { color: var(--color-primary); }
1319
1319
  .fdy-filterbar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-3) var(--space-4);}
1320
1320
  /* Composed with a table toolbar (one element, both classes): the fields decide the alignment.
1321
1321
  * `.fdy-table-toolbar` sets align-items:center (right for bare buttons) at equal specificity, and
1322
- * being later in the bundle it would otherwise win floating labelled fields' neighbours out of line.
1322
+ * being later in the bundle it would otherwise win, floating labelled fields' neighbours out of line.
1323
1323
  * This 2-class selector (0,2,0) settles it toward the filterbar's flex-end regardless of source order. */
1324
1324
  .fdy-table-toolbar.fdy-filterbar{align-items:flex-end;}
1325
1325
  /* Fields are direct children. Drop the standalone field's 22rem cap; default to the
@@ -1327,7 +1327,7 @@ a { color: var(--color-primary); }
1327
1327
  .fdy-filterbar>.fdy-field{max-width:none;flex:0 0 auto;width:12rem;}
1328
1328
  /* Controls fill their field's column (drop the standalone input-group/combo caps). */
1329
1329
  .fdy-filterbar>.fdy-field .fdy-input-group,.fdy-filterbar>.fdy-field .fdy-combo{max-width:none;width:100%;}
1330
- /* Column-width rhythm pick one per field so bars across the app line up. */
1330
+ /* Column-width rhythm, pick one per field so bars across the app line up. */
1331
1331
  .fdy-filterbar>.fdy-field--w-sm{width:9rem;}
1332
1332
  .fdy-filterbar>.fdy-field--w-lg{width:15rem;}
1333
1333
  .fdy-filterbar>.fdy-field--w-xl{width:18rem;}
@@ -1337,11 +1337,11 @@ a { color: var(--color-primary); }
1337
1337
  .fdy-filterbar>.fdy-field--w-grow{flex:1 1 16rem;width:auto;min-width:12rem;}
1338
1338
  /* Trailing actions (buttons) pushed to the end, aligned with the controls' baseline.
1339
1339
  * Wrap note: a wrapped bar breaks flex lines on each item's *flex-basis* (the grow field's
1340
- * `flex:1 1 16rem` reserves 16rem for line-breaking), NOT its rendered width so the grow basis,
1340
+ * `flex:1 1 16rem` reserves 16rem for line-breaking), NOT its rendered width, so the grow basis,
1341
1341
  * not the visible width, decides the wrap point. Margins can't keep the actions off a lonely wrapped
1342
1342
  * row; to avoid stranded actions, tune the --w-grow basis or drop a field so the bar fits one row. */
1343
- /* A control that carries NO label of its own a checkbox, a switch, a lone
1344
- button in a bar whose `align-items:flex-end` exists to line up LABELLED
1343
+ /* A control that carries NO label of its own, a checkbox, a switch, a lone
1344
+ button, in a bar whose `align-items:flex-end` exists to line up LABELLED
1345
1345
  fields. Flex-end puts such a control's bottom on the inputs' bottom, so a
1346
1346
  20px checkbox sits well below the 32px input's centre line and reads as
1347
1347
  dropped. Giving it the height of a control lands its box exactly where an
@@ -1349,7 +1349,7 @@ a { color: var(--color-primary); }
1349
1349
  line. Raised from IDU_EMATE_APPL_WEB (#014). */
1350
1350
  .fdy-filterbar>.fdy-check,.fdy-filterbar>.fdy-radio,.fdy-filterbar>.fdy-switch,.fdy-filterbar>.fdy-btn{min-height:var(--control-h);}
1351
1351
  .fdy-filterbar__actions{display:flex;align-items:center;gap:var(--space-2);margin-left:auto;}
1352
- /* Opt-in: sit the actions inline right after the last field instead of at the far edge so when the
1352
+ /* Opt-in: sit the actions inline right after the last field instead of at the far edge, so when the
1353
1353
  * bar wraps they read as attached to the filters rather than shoved to the right of a lonely row. */
1354
1354
  .fdy-filterbar--actions-inline .fdy-filterbar__actions{margin-left:0;}
1355
1355
  /* Stack on narrow screens so nothing overflows the row. */
@@ -1358,16 +1358,16 @@ a { color: var(--color-primary); }
1358
1358
  .fdy-filterbar__actions{width:100%;margin-left:0;}
1359
1359
  }
1360
1360
 
1361
- /* Freeday Form grid (document/header fields laid out in aligned columns).
1361
+ /* Freeday. Form grid (document/header fields laid out in aligned columns).
1362
1362
  * The two-dimensional counterpart to the single-column .fdy-field stack and the
1363
1363
  * one-row .fdy-filterbar: related fields sit in columns that reflow to one on narrow
1364
1364
  * screens. Fields are the standard .fdy-field; a field with --full spans the whole row
1365
- * (e.g. an address or note). Zero JS pure layout over .fdy-field. */
1365
+ * (e.g. an address or note). Zero JS, pure layout over .fdy-field. */
1366
1366
  .fdy-form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(14rem,1fr));gap:var(--space-4) var(--space-5);}
1367
1367
  .fdy-form-grid>.fdy-field{max-width:none;}
1368
1368
  .fdy-form-grid>.fdy-field--full{grid-column:1/-1;}
1369
1369
 
1370
- /* Freeday Input group (prefix/suffix addons, e.g. Rp / % / icon) */
1370
+ /* Freeday. Input group (prefix/suffix addons, e.g. Rp / % / icon) */
1371
1371
  .fdy-input-group{display:flex;align-items:stretch;height:var(--control-h);max-width:22rem;background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);overflow:hidden;transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
1372
1372
  .fdy-input-group:hover{border-color:var(--color-text-muted);}
1373
1373
  .fdy-input-group:focus-within{border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
@@ -1380,34 +1380,34 @@ a { color: var(--color-primary); }
1380
1380
  .fdy-input-group .fdy-input:focus{box-shadow:none;}
1381
1381
  .fdy-input-group__addon{display:inline-flex;align-items:center;padding:0 var(--space-3);background:var(--color-surface-2);color:var(--color-text-muted);font-size:var(--text-sm);white-space:nowrap;border-right:var(--bw) solid var(--color-border);}
1382
1382
  .fdy-input-group__addon:last-child{border-right:0;border-left:var(--bw) solid var(--color-border);}
1383
- /* Borderless leading icon (e.g. search magnifier) self-contained like __btn so it
1383
+ /* Borderless leading icon (e.g. search magnifier), self-contained like __btn so it
1384
1384
  centers standalone (no base .fdy-input-group__addon required). */
1385
1385
  .fdy-input-group__addon--icon{display:inline-flex;align-items:center;background:transparent;border:0;padding:0 0 0 var(--space-3);color:var(--color-text-subtle);}
1386
1386
  .fdy-input-group__addon--icon svg{display:block;width:1rem;height:1rem;}
1387
- /* The leading icon already provides the left inset don't let the input add its full left padding on top. */
1387
+ /* The leading icon already provides the left inset, don't let the input add its full left padding on top. */
1388
1388
  .fdy-input-group__addon--icon + .fdy-input{padding-left:var(--space-2);}
1389
- /* Trailing action button (e.g. choose-from-list magnifier) a real, focusable control. */
1389
+ /* Trailing action button (e.g. choose-from-list magnifier), a real, focusable control. */
1390
1390
  .fdy-input-group__btn{display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--control-h);align-self:stretch;border:0;border-left:var(--bw) solid var(--color-border);background:var(--color-surface-2);color:var(--color-text-muted);cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),color var(--dur-fast) var(--ease-standard);}
1391
1391
  .fdy-input-group__btn:hover{background:var(--color-surface-3);color:var(--color-text);}
1392
1392
  .fdy-input-group__btn:focus-visible{outline:none;color:var(--color-text);box-shadow:inset 0 0 0 2px var(--color-primary);}
1393
1393
  .fdy-input-group__btn svg{display:block;width:1.1rem;height:1.1rem;}
1394
1394
  /* Leading action button (the "−" of a number stepper): the divider belongs on the other side. */
1395
1395
  .fdy-input-group__btn:first-child{border-left:0;border-right:var(--bw) solid var(--color-border);}
1396
- /* At a bound, or on a disabled/read-only field, the button must look as inert as it acts same dim
1396
+ /* At a bound, or on a disabled/read-only field, the button must look as inert as it acts, same dim
1397
1397
  + not-allowed as .fdy-input:disabled. :hover still matches a disabled button, so it is withdrawn. */
1398
1398
  .fdy-input-group__btn:disabled{opacity:.5;cursor:not-allowed;}
1399
1399
  .fdy-input-group__btn:disabled:hover{background:var(--color-surface-2);color:var(--color-text-muted);}
1400
- /* Error state must win over the neutralizing rules above promote to the wrapper. */
1400
+ /* Error state must win over the neutralizing rules above, promote to the wrapper. */
1401
1401
  .fdy-input-group:has(.fdy-input--error),.fdy-input-group:has(.fdy-combo--error),.fdy-input-group:has([aria-invalid="true"]){border-color:var(--color-danger);}
1402
1402
  .fdy-input-group:has(.fdy-input--error):focus-within,.fdy-input-group:has(.fdy-combo--error):focus-within,.fdy-input-group:has([aria-invalid="true"]):focus-within{border-color:var(--color-danger);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-danger) 26%,transparent);}
1403
1403
 
1404
- /* Freeday Field & Input */
1404
+ /* Freeday. Field & Input */
1405
1405
  .fdy-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
1406
- /* A grouped field is legitimately a <fieldset> (it needs a <legend>) e.g. a radio/checkbox group.
1406
+ /* A grouped field is legitimately a <fieldset> (it needs a <legend>), e.g. a radio/checkbox group.
1407
1407
  Neutralise the UA groove border / padding / min-inline-size so it lays out identically to the
1408
1408
  <div> form (min-inline-size:min-content would otherwise block the filterbar column widths). */
1409
1409
  fieldset.fdy-field{border:0;margin:0;padding:0;min-inline-size:0;}
1410
- /* The rendered legend is laid out outside the fieldset's flex flow, so `gap` never reaches it
1410
+ /* The rendered legend is laid out outside the fieldset's flex flow, so `gap` never reaches it,
1411
1411
  measured 0px between legend and first control. This is the spacing contract a grouped field
1412
1412
  needs; it matches .fdy-field's own gap so the group reads as one rhythm. */
1413
1413
  fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
@@ -1425,40 +1425,40 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1425
1425
  .fdy-input[readonly]:hover,.fdy-textarea[readonly]:hover{border-color:var(--color-border);}
1426
1426
  .fdy-input[readonly]:focus-visible,.fdy-textarea[readonly]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
1427
1427
  /* Numbers (#44): the user agent draws its own spin buttons inside the field, in OS widget colours no
1428
- theme reaches on a dark surface they read as a light-grey artefact glued to a themed field, and
1428
+ theme reaches, on a dark surface they read as a light-grey artefact glued to a themed field, and
1429
1429
  it is the only unthemed control on the page. Removed so .fdy-input looks the same whatever its
1430
1430
  type. `appearance:textfield` is the Firefox half, the pseudo-element the Blink/WebKit half; both
1431
- are needed. This trades away the mouse-only increment arrow keys on a focused number input still
1431
+ are needed. This trades away the mouse-only increment, arrow keys on a focused number input still
1432
1432
  work, and [data-fdy-number] gives the affordance back on the kit's terms. */
1433
1433
  .fdy-input[type="number"]{appearance:textfield;-webkit-appearance:textfield;}
1434
1434
  .fdy-input[type="number"]::-webkit-outer-spin-button,.fdy-input[type="number"]::-webkit-inner-spin-button{appearance:none;-webkit-appearance:none;margin:0;}
1435
1435
  .fdy-input--error,.fdy-input[aria-invalid="true"],.fdy-textarea[aria-invalid="true"]{border-color:var(--color-danger);}
1436
1436
  .fdy-input--error:focus,.fdy-input[aria-invalid="true"]:focus,.fdy-textarea[aria-invalid="true"]:focus{border-color:var(--color-danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-danger) 26%,transparent);}
1437
1437
  /* Required marker. The control already carries `required`, so the asterisk is decoration: the
1438
- `/ ""` is CSS alt text the glyph is painted but exposes NOTHING to the accessibility tree, so a
1438
+ `/ ""` is CSS alt text, the glyph is painted but exposes NOTHING to the accessibility tree, so a
1439
1439
  screen reader does not read "star" after every label. That is stricter than a <span> an app has to
1440
1440
  remember to mark aria-hidden, and it cannot be forgotten. */
1441
1441
  .fdy-label--required::after{content:"*" / "";margin-left:var(--space-1);color:var(--color-danger);}
1442
1442
  .fdy-help{font-size:var(--text-xs);color:var(--color-text-muted);}
1443
1443
  .fdy-help--error{color:var(--color-danger);}
1444
1444
 
1445
- /* Freeday Keyboard key */
1445
+ /* Freeday. Keyboard key */
1446
1446
  .fdy-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:1.5rem;height:1.5rem;padding:0 var(--space-2);font-family:var(--font-mono);font-size:var(--text-xs);color:var(--color-text);background:var(--color-surface-2);border:var(--bw) solid var(--color-border-strong);border-bottom-width:2px;border-radius:var(--radius-sm);}
1447
1447
 
1448
- /* Freeday List: the FLAT row container.
1448
+ /* Freeday. List: the FLAT row container.
1449
1449
  *
1450
1450
  * USAGE.md §3 puts list rows among the surfaces that should be flat, but until now the only
1451
- * container the kit shipped was .fdy-card which carries --shadow-lift (a real 34px lift). A
1451
+ * container the kit shipped was .fdy-card, which carries --shadow-lift (a real 34px lift). A
1452
1452
  * responsive table that becomes a list below `md` therefore had to choose between a stack of
1453
1453
  * shadowed cards (against the doctrine) or a hand-built box, which needs a colour and so escapes
1454
1454
  * the token system. This is that missing container: one flat surface, hairline dividers, no shadow.
1455
1455
  *
1456
1456
  * Not to be confused with .fdy-list-reset (base.css), which only strips UA bullets/indent.
1457
1457
  * Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
1458
- /* position:relative containing block for out-of-flow row content (see base.css). */
1458
+ /* position:relative, containing block for out-of-flow row content (see base.css). */
1459
1459
  .fdy-list{position:relative;list-style:none;margin:0;padding:0;background:var(--color-surface);border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;}
1460
1460
  /* A row that hosts a stretched target has to be the anchor itself (#008 §1). Without this the
1461
- overlay resolves against .fdy-list which IS positioned so EVERY row's target covers the whole
1461
+ overlay resolves against .fdy-list, which IS positioned, so EVERY row's target covers the whole
1462
1462
  list and the last one in the DOM wins: measured, a click on row one opens row two, under row one's
1463
1463
  name, which looks exactly like working software. Opt-in via :has(), so a row that doesn't use the
1464
1464
  pattern is untouched and nothing an app absolutely positions inside a plain row starts resolving
@@ -1467,7 +1467,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1467
1467
  .fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
1468
1468
  /* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
1469
1469
  * of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
1470
- * <li>s a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
1470
+ * <li>s, a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
1471
1471
  .fdy-list__row + .fdy-list__row,
1472
1472
  .fdy-list > * + * > .fdy-list__row{border-top:var(--bw) solid var(--color-border-muted);}
1473
1473
  /* A row that is itself the control: render it as <button>/<a> and the UA box is reset without
@@ -1476,7 +1476,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1476
1476
  .fdy-list__row--button:hover,.fdy-list__row--interactive:hover{background:var(--color-surface-2);}
1477
1477
  .fdy-list__row--button:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary);}
1478
1478
  .fdy-list__row--interactive{cursor:pointer;}
1479
- /* Disabled row. The reset above adopts the UA button box, but not its disabled state so a row
1479
+ /* Disabled row. The reset above adopts the UA button box, but not its disabled state, so a row
1480
1480
  disabled mid-flight (a redirect in progress, say) kept lighting up and kept a pointer cursor:
1481
1481
  a control answering the pointer while refusing input. Same contract as every other disabled
1482
1482
  control in the kit (dim + not-allowed), hover withdrawn, both the native and the aria- form. */
@@ -1489,7 +1489,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1489
1489
  .fdy-list__aside{flex:none;display:flex;align-items:center;gap:var(--space-2);}
1490
1490
  /* Density: rows tighten with the rest of the kit (--space-* mid-range steps under compact). */
1491
1491
 
1492
- /* Freeday Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
1492
+ /* Freeday. Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
1493
1493
  .fdy-menu-wrap{position:relative;display:inline-flex;}
1494
1494
  .fdy-menu{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:11rem;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
1495
1495
  .fdy-menu[hidden]{display:none;}
@@ -1497,7 +1497,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1497
1497
  .fdy-menu__item{display:flex;align-items:center;gap:var(--space-2);width:100%;padding:var(--space-2) var(--space-3);border:0;background:transparent;border-radius:var(--radius-sm);font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);text-align:left;cursor:pointer;white-space:nowrap;}
1498
1498
  .fdy-menu__item:hover,.fdy-menu__item.is-highlighted{background:var(--color-surface-3);}
1499
1499
  /* Focus is not hover. The enhancer moves real DOM focus (freeday-menu.js `.focus()`), so this is
1500
- the only thing a keyboard user has to go on with the hover fill alone, arrowing through the
1500
+ the only thing a keyboard user has to go on, with the hover fill alone, arrowing through the
1501
1501
  menu is invisible. Same ring as .fdy-nav__item, which got this right. Reported twice. */
1502
1502
  .fdy-menu__item:focus-visible{outline:none;background:var(--color-surface-3);box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
1503
1503
  .fdy-menu__item:disabled{opacity:.5;cursor:not-allowed;background:transparent;}
@@ -1506,7 +1506,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1506
1506
  .fdy-menu__sep{height:0;margin:var(--space-1) 0;border:0;border-top:var(--bw) solid var(--color-border-muted);}
1507
1507
  @media (prefers-reduced-motion:no-preference){.fdy-menu:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
1508
1508
 
1509
- /* Split button main action + attached menu toggle (caret) */
1509
+ /* Split button, main action + attached menu toggle (caret) */
1510
1510
  .fdy-btn-split{position:relative;display:inline-flex;border-radius:var(--radius-md);transition:transform var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
1511
1511
  /* One control: the two halves must stay coplanar. Neutralize each segment's own
1512
1512
  lift + drop-shadow (they would misalign the seam / glow a single half); the
@@ -1521,7 +1521,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1521
1521
  .fdy-btn-split__toggle:hover,.fdy-btn-split__toggle:focus-visible{z-index:1;}
1522
1522
  .fdy-btn-split__toggle svg{display:block;width:1rem;height:1rem;}
1523
1523
 
1524
- /* Freeday Modal (native <dialog>) */
1524
+ /* Freeday. Modal (native <dialog>) */
1525
1525
  .fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - var(--space-16));padding:0;border:var(--bw) solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-surface);color:var(--color-text);box-shadow:var(--shadow-lift-hover);overflow:hidden;flex-direction:column;opacity:0;transform:translateY(8px) scale(.98);transition:opacity var(--dur-base) var(--ease-spring),transform var(--dur-base) var(--ease-spring),overlay var(--dur-base) allow-discrete,display var(--dur-base) allow-discrete;}
1526
1526
  .fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
1527
1527
  .fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
@@ -1544,7 +1544,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1544
1544
  .fdy-modal[open]::backdrop{opacity:0;}
1545
1545
  }
1546
1546
 
1547
- /* Freeday Pagination */
1547
+ /* Freeday. Pagination */
1548
1548
  .fdy-pagination__list{display:flex;align-items:center;gap:var(--space-1);list-style:none;margin:0;padding:0;}
1549
1549
  .fdy-pagination__link{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;padding:0 var(--space-2);border-radius:var(--radius-md);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text);background:transparent;border:var(--bw) solid transparent;text-decoration:none;cursor:pointer;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
1550
1550
  .fdy-pagination__link:hover{background:var(--color-surface-2);border-color:var(--color-border);}
@@ -1553,7 +1553,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1553
1553
  .fdy-pagination__link[aria-disabled="true"],.fdy-pagination__link:disabled{opacity:.5;cursor:not-allowed;pointer-events:none;}
1554
1554
  .fdy-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;color:var(--color-text-subtle);}
1555
1555
 
1556
- /* Freeday Progress */
1556
+ /* Freeday. Progress */
1557
1557
  .fdy-progress{height:.5rem;border-radius:var(--radius-full);background:var(--color-surface-3);overflow:hidden;}
1558
1558
  .fdy-progress__bar{height:100%;border-radius:inherit;background:var(--color-primary);transition:width var(--dur-base) var(--ease-standard);}
1559
1559
  .fdy-progress--indeterminate{position:relative;}
@@ -1561,7 +1561,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1561
1561
  @keyframes fdy-progress-indet{0%{margin-left:-40%;}100%{margin-left:100%;}}
1562
1562
  @media (prefers-reduced-motion:reduce){.fdy-progress--indeterminate .fdy-progress__bar{animation:none;width:100%;opacity:.5;}}
1563
1563
 
1564
- /* Freeday Rating (star rating). Interactive = native radio group (arrow keys, form-assoc);
1564
+ /* Freeday. Rating (star rating). Interactive = native radio group (arrow keys, form-assoc);
1565
1565
  * filled vs empty differ by fill-vs-outline (not colour alone). Enhanced by freeday-rating.js. */
1566
1566
  .fdy-rating{display:inline-flex;gap:var(--space-1);}
1567
1567
  .fdy-rating__star{display:inline-flex;line-height:0;cursor:pointer;}
@@ -1574,17 +1574,17 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1574
1574
  .fdy-rating--disabled .fdy-rating__star{cursor:not-allowed;}
1575
1575
  .fdy-rating--disabled .fdy-rating__star svg{opacity:.5;}
1576
1576
  /* Invalid: tint the (outline) empty stars danger so a required-but-empty rating reads as an error,
1577
- paired with a help/error message never colour alone. */
1577
+ paired with a help/error message, never colour alone. */
1578
1578
  .fdy-rating--error .fdy-rating__star svg{stroke:var(--color-danger);}
1579
1579
  .fdy-rating--sm .fdy-rating__star svg{width:1.1rem;height:1.1rem;}
1580
1580
 
1581
- /* Freeday Selection controls */
1581
+ /* Freeday. Selection controls */
1582
1582
  /* `flex:none` on every box, track and standalone checkbox is load-bearing, not tidiness.
1583
1583
  `.fdy-check` is an inline-flex row of [box, label], and a flex item with a WIDTH is still
1584
- shrinkable so as soon as the label wrapped to a second line the box gave up horizontal space
1584
+ shrinkable, so as soon as the label wrapped to a second line the box gave up horizontal space
1585
1585
  to it and rendered an 18px-tall rectangle 13px wide. Squashed, off-square, and different from
1586
1586
  every sibling whose label happened to be short: a form of five checkboxes showed three sizes.
1587
- Nothing in the declarations looked wrong, because the width was never overridden it was
1587
+ Nothing in the declarations looked wrong, because the width was never overridden, it was
1588
1588
  negotiated away. */
1589
1589
  .fdy-check,.fdy-radio,.fdy-switch{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;}
1590
1590
  .fdy-check input,.fdy-radio input{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
@@ -1602,7 +1602,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1602
1602
  .fdy-switch input:checked{background:var(--color-primary);}
1603
1603
  .fdy-switch input:checked::before{transform:translateX(calc(var(--control-switch-w) - var(--control-switch-h)));}
1604
1604
 
1605
- /* Standalone checkbox (e.g. table row/select-all) supports :indeterminate */
1605
+ /* Standalone checkbox (e.g. table row/select-all), supports :indeterminate */
1606
1606
  .fdy-checkbox{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);border-radius:var(--radius-sm);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
1607
1607
  .fdy-checkbox::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
1608
1608
  .fdy-checkbox:checked,.fdy-checkbox:indeterminate{background:var(--color-primary);border-color:var(--color-primary);}
@@ -1611,7 +1611,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1611
1611
  .fdy-checkbox:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1612
1612
  .fdy-checkbox:disabled{opacity:.5;cursor:not-allowed;}
1613
1613
 
1614
- /* Freeday Skeleton */
1614
+ /* Freeday. Skeleton */
1615
1615
  .fdy-skeleton{display:block;border-radius:var(--radius-sm);background:var(--color-surface-3);background-image:linear-gradient(90deg,transparent,color-mix(in srgb,var(--color-surface) 60%,transparent),transparent);background-size:200% 100%;background-repeat:no-repeat;animation:fdy-skeleton 1.4s ease-in-out infinite;}
1616
1616
  .fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
1617
1617
  .fdy-skeleton--title{height:1.2rem;width:40%;}
@@ -1624,7 +1624,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1624
1624
  @keyframes fdy-skeleton{to{background-position:-200% 0;}}
1625
1625
  @media (prefers-reduced-motion:reduce){.fdy-skeleton{animation:none;}}
1626
1626
 
1627
- /* Freeday Slider (native <input type="range"> themed via accent-color: keeps native
1627
+ /* Freeday. Slider (native <input type="range"> themed via accent-color: keeps native
1628
1628
  * keyboard + ARIA, filled track and thumb come free). Optional value output + scale.
1629
1629
  * freeday-slider.js just mirrors the value into an <output for="…">. */
1630
1630
  .fdy-slider{width:100%;max-width:22rem;height:1.5rem;accent-color:var(--color-primary);cursor:pointer;}
@@ -1637,14 +1637,14 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1637
1637
  .fdy-slider__value{font-family:var(--font-mono);font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--color-primary-strong);font-variant-numeric:tabular-nums;}
1638
1638
  .fdy-slider__scale{display:flex;justify-content:space-between;font-size:var(--text-xs);color:var(--color-text-muted);font-variant-numeric:tabular-nums;}
1639
1639
 
1640
- /* Freeday Spinner */
1640
+ /* Freeday. Spinner */
1641
1641
  .fdy-spinner{display:inline-block;width:1.5rem;height:1.5rem;border:2.5px solid var(--color-border-strong);border-top-color:var(--color-primary);border-radius:var(--radius-full);animation:fdy-spin .7s linear infinite;}
1642
1642
  .fdy-spinner--sm{width:1rem;height:1rem;border-width:2px;}
1643
1643
  .fdy-spinner--lg{width:2.5rem;height:2.5rem;border-width:3px;}
1644
1644
  @keyframes fdy-spin{to{transform:rotate(360deg);}}
1645
1645
  @media (prefers-reduced-motion:reduce){.fdy-spinner{animation-duration:2s;}}
1646
1646
 
1647
- /* Freeday States (empty / loading / error placeholders) */
1647
+ /* Freeday. States (empty / loading / error placeholders) */
1648
1648
  .fdy-state{display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--space-2);padding:var(--space-10) var(--space-6);}
1649
1649
  .fdy-state__icon{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;color:var(--color-text-subtle);}
1650
1650
  .fdy-state__icon svg{width:100%;height:100%;}
@@ -1653,7 +1653,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1653
1653
  .fdy-state__actions{margin-top:var(--space-2);display:flex;gap:var(--space-2);flex-wrap:wrap;justify-content:center;}
1654
1654
  .fdy-state--danger .fdy-state__icon{color:var(--color-danger-strong);}
1655
1655
 
1656
- /* Freeday Stepper / Wizard (progress header + optional panels).
1656
+ /* Freeday. Stepper / Wizard (progress header + optional panels).
1657
1657
  * Enhanced by freeday-stepper.js for prev/next navigation. */
1658
1658
  .fdy-stepper{display:flex;list-style:none;margin:0;padding:0;}
1659
1659
  .fdy-step{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;text-align:center;min-width:0;}
@@ -1663,7 +1663,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1663
1663
  .fdy-step__btn{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:0 var(--space-2);background:none;border:0;font:inherit;color:inherit;cursor:pointer;max-width:100%;}
1664
1664
  .fdy-step__btn:disabled{cursor:default;}
1665
1665
  /* The marker carries the lift itself, NOT `__btn`. The connector above is position:absolute with
1666
- z-index:0, and a positioned box paints after in-flow inline content in the same stacking context
1666
+ z-index:0, and a positioned box paints after in-flow inline content in the same stacking context,
1667
1667
  so a marker that is merely inline (an indicator-only stepper with no `__btn`) gets painted THROUGH
1668
1668
  by the connector line. Lifting the part that must always exist makes that assembly impossible;
1669
1669
  `__btn`'s own z-index stays, harmlessly, for the navigable case. */
@@ -1680,8 +1680,8 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1680
1680
  .fdy-step-panels{margin-top:var(--space-5);}
1681
1681
  .fdy-step-nav{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-5);}
1682
1682
 
1683
- /* Freeday Table */
1684
- /* position:relative on every scroller/clipper it makes the box the containing block for its own
1683
+ /* Freeday. Table */
1684
+ /* position:relative on every scroller/clipper, it makes the box the containing block for its own
1685
1685
  absolutely positioned content (.fdy-visually-hidden above all), which `overflow` alone does NOT
1686
1686
  clip. Without it a hidden label in a wide table scrolls the whole page. See base.css. */
1687
1687
  .fdy-table-wrap{position:relative;overflow-x:auto;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);background:var(--color-surface);}
@@ -1692,7 +1692,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1692
1692
  .fdy-table tbody tr:last-child td{border-bottom:0;}
1693
1693
  .fdy-table tbody tr{transition:background-color var(--dur-fast) var(--ease-standard);}
1694
1694
  .fdy-table tbody tr:hover{background:var(--color-surface-2);}
1695
- /* Monospace data ids, codes, IPs, timestamps. Alignment-neutral so it works in any cell
1695
+ /* Monospace data, ids, codes, IPs, timestamps. Alignment-neutral so it works in any cell
1696
1696
  or inline (`<span class="fdy-mono">`), not just numeric columns. */
1697
1697
  .fdy-mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums;}
1698
1698
  /* Numeric cells are mono + right-aligned. Kept self-contained (not composed from .fdy-mono)
@@ -1719,7 +1719,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1719
1719
  .fdy-table__selcol{width:1px;white-space:nowrap;}
1720
1720
  .fdy-table tbody tr[aria-selected="true"],.fdy-table tbody tr[aria-selected="true"]:hover{background:var(--color-primary-soft);}
1721
1721
 
1722
- /* Frozen header row and/or first column a rate matrix, a timetable, any grid read
1722
+ /* Frozen header row and/or first column, a rate matrix, a timetable, any grid read
1723
1723
  against two axes at once.
1724
1724
 
1725
1725
  This modifier exists because `position:sticky` alone is NOT enough. With
@@ -1733,12 +1733,12 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1733
1733
  /* Freezing the HEADER ROW is its own modifier because it needs a vertical
1734
1734
  scrollport (`.fdy-table-scroll--frozen`). A table that only freezes columns
1735
1735
  scrolls with the PAGE, and `top:0` against the page sticks the header under
1736
- the viewport edge over the app's own top bar which is worse than not
1736
+ the viewport edge, over the app's own top bar, which is worse than not
1737
1737
  freezing it. Ask for the axis you actually have. */
1738
1738
  .fdy-table--sticky-head thead th{position:sticky;top:0;z-index:2;border-bottom:var(--bw) solid var(--color-border);}
1739
1739
 
1740
1740
  /* Freezing a COLUMN: put `.fdy-table__freeze` on every cell of it, header
1741
- included, and give each frozen column its own `--fdy-freeze-left` 0 for the
1741
+ included, and give each frozen column its own `--fdy-freeze-left`. 0 for the
1742
1742
  first, the summed widths of the frozen columns left of it after that. One
1743
1743
  frozen column needs no variable at all. Mark the last one `--edge` so the
1744
1744
  reader can see where the frozen block ends.
@@ -1747,12 +1747,12 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1747
1747
  widths, which CSS cannot sum, and hard-coding them here would only work for
1748
1748
  tables whose columns are all the same width. */
1749
1749
  .fdy-table--sticky .fdy-table__freeze{position:sticky;left:var(--fdy-freeze-left,0);}
1750
- /* A frozen body cell slides OVER the data, so it must be OPAQUE otherwise the
1750
+ /* A frozen body cell slides OVER the data, so it must be OPAQUE, otherwise the
1751
1751
  cells beneath show through it. A `th` already carries the header tint from the
1752
1752
  base rule, and that tint is usually the whole point: it is what marks a column
1753
1753
  as identity rather than data. So only a `td` is given a surface here. A table
1754
- that wants its frozen `th` to read as a body row instead a rate matrix, where
1755
- a hovered row should band unbroken across the freeze line says so itself. */
1754
+ that wants its frozen `th` to read as a body row instead, a rate matrix, where
1755
+ a hovered row should band unbroken across the freeze line, says so itself. */
1756
1756
  .fdy-table--sticky tbody .fdy-table__freeze{z-index:1;}
1757
1757
  .fdy-table--sticky tbody td.fdy-table__freeze{background:var(--color-surface);}
1758
1758
  .fdy-table--sticky tbody tr:hover td.fdy-table__freeze{background:var(--color-surface-2);}
@@ -1775,19 +1775,19 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1775
1775
  .fdy-table-footer{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-3) var(--space-4);border-top:var(--bw) solid var(--color-border);}
1776
1776
  .fdy-table-footer__info{font-size:var(--text-sm);color:var(--color-text-muted);}
1777
1777
  /* Rows per page. `margin-left:auto` eats the free space, so the size control and the pager travel
1778
- together on the right while the range stays left no wrapper element, which is what keeps the
1778
+ together on the right while the range stays left, no wrapper element, which is what keeps the
1779
1779
  footer's DOM byte-identical for the tables that don't offer one (#008). The control is the kit's
1780
1780
  own `.fdy-combo`, never a native <select>: an OS menu is unthemeable, and on macOS it drops a
1781
- dark panel into a light page which is the reason FdyCombo exists at all. Sized to its content:
1781
+ dark panel into a light page, which is the reason FdyCombo exists at all. Sized to its content:
1782
1782
  `.fdy-combo` is display:block at max-width 22rem, which in a footer is a bar-wide box for two
1783
- digits. Its HEIGHT is left alone on purpose `--control-h`, the same as every other control on
1783
+ digits. Its HEIGHT is left alone on purpose, `--control-h`, the same as every other control on
1784
1784
  the page. It briefly carried 2.25rem to match the pager links beside it, which made the one
1785
1785
  combobox in a compact app 4px taller than all the others; a pager link is a nav button, and the
1786
1786
  thing this must agree with is the form controls. */
1787
1787
  .fdy-table-footer__size{display:inline-flex;align-items:center;gap:var(--space-2);margin-left:auto;font-size:var(--text-sm);color:var(--color-text-muted);}
1788
1788
  .fdy-table-footer__size .fdy-combo{width:5.5rem;}
1789
1789
 
1790
- /* Column filter funnel button injected (enhancer) or rendered (FdyTable) into a filterable <th> */
1790
+ /* Column filter, funnel button injected (enhancer) or rendered (FdyTable) into a filterable <th> */
1791
1791
  .fdy-table__filterwrap{display:inline-flex;align-items:center;vertical-align:middle;}
1792
1792
  .fdy-table__filterbtn{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;margin-left:var(--space-1);padding:0;vertical-align:middle;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-subtle);cursor:pointer;}
1793
1793
  .fdy-table__filterbtn:hover{background:var(--color-surface-3);color:var(--color-text);}
@@ -1804,7 +1804,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1804
1804
  .fdy-filter__range .fdy-input{width:6rem;}
1805
1805
  .fdy-filter__foot{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-3);}
1806
1806
 
1807
- /* Bulk-action bar appears above the table body once rows are selected */
1807
+ /* Bulk-action bar, appears above the table body once rows are selected */
1808
1808
  .fdy-table-bulkbar{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) var(--space-4);background:var(--color-primary-soft);border-bottom:var(--bw) solid var(--color-border);color:var(--color-primary-strong);font-size:var(--text-sm);}
1809
1809
  .fdy-table-bulkbar[hidden]{display:none;}
1810
1810
  .fdy-table-bulkbar__count{font-weight:var(--weight-semibold);white-space:nowrap;}
@@ -1812,14 +1812,14 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1812
1812
  .fdy-table-bulkbar__actions{display:flex;gap:var(--space-2);}
1813
1813
 
1814
1814
  /* Controls inside a data row. `.fdy-input` and friends are width:100%, which in an auto-layout
1815
- table means they contribute NO intrinsic width the column shrinks to whatever the header text
1815
+ table means they contribute NO intrinsic width, the column shrinks to whatever the header text
1816
1816
  needs and the control collapses with it. A NOTE column ends up a box too narrow to read what you
1817
1817
  typed into it. The floor is per-control rather than on the column, because the table does not
1818
1818
  know which of its columns hold controls. */
1819
1819
  .fdy-table td>.fdy-input,.fdy-table td>.fdy-textarea,.fdy-table td>.fdy-combo,.fdy-table td>.fdy-input-group,.fdy-table td>.fdy-datepicker,.fdy-table td>.fdy-timepicker{min-width:7rem;}
1820
1820
 
1821
- /* Freeday Tabs (WAI-ARIA APG) */
1822
- /* position:relative containing block for out-of-flow content in the tabs (see base.css). */
1821
+ /* Freeday. Tabs (WAI-ARIA APG) */
1822
+ /* position:relative, containing block for out-of-flow content in the tabs (see base.css). */
1823
1823
  .fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);border-bottom:var(--bw) solid var(--color-border);overflow-x:auto;}
1824
1824
  .fdy-tabs__tab{appearance:none;border:0;background:transparent;cursor:pointer;white-space:nowrap;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);padding:var(--space-3) var(--space-4);border-bottom:2px solid transparent;margin-bottom:-1px;transition:color var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
1825
1825
  .fdy-tabs__tab:hover{color:var(--color-text);}
@@ -1827,7 +1827,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1827
1827
  .fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
1828
1828
  /* `aria-current="page"` is honoured alongside `aria-selected="true"` so the tab look can be used for
1829
1829
  ROUTE-driven sub-navigation (/settings/profile · /settings/billing), where the tabs are real links.
1830
- A link cannot be `aria-selected` that is invalid ARIA on an anchor; `aria-current` is the only
1830
+ A link cannot be `aria-selected`, that is invalid ARIA on an anchor; `aria-current` is the only
1831
1831
  correct marker, and it is what a router sets. Use `.fdy-tabs` + `freeday-tabs.js` (roving tabindex,
1832
1832
  a panel per tab) for in-page tabs; use these classes on plain links for routed ones, and
1833
1833
  `.fdy-nav--horizontal` for an application's PRIMARY navigation. */
@@ -1838,7 +1838,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1838
1838
  /* Reveal the newly-shown panel on tab switch (fade + rise), reduced-motion gated. */
1839
1839
  @media (prefers-reduced-motion:no-preference){.fdy-tabs__panel:not([hidden]){animation:fdy-combo-in var(--dur-base) var(--ease-standard);}}
1840
1840
 
1841
- /* Freeday Timeline (vertical event feed: dot markers on a connector line). Pure CSS. */
1841
+ /* Freeday. Timeline (vertical event feed: dot markers on a connector line). Pure CSS. */
1842
1842
  .fdy-timeline{list-style:none;margin:0;padding:0;}
1843
1843
  .fdy-timeline__item{position:relative;padding:0 0 var(--space-5) var(--space-6);border-left:2px solid var(--color-border);}
1844
1844
  .fdy-timeline__item:last-child{border-left-color:transparent;padding-bottom:0;}
@@ -1851,7 +1851,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1851
1851
  .fdy-timeline__title{margin:0;font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--color-text);}
1852
1852
  .fdy-timeline__text{margin:var(--space-1) 0 0;font-size:var(--text-sm);color:var(--color-text-muted);line-height:var(--leading-normal);}
1853
1853
 
1854
- /* Freeday Time picker (input-styled trigger + scrollable time-list popover).
1854
+ /* Freeday. Time picker (input-styled trigger + scrollable time-list popover).
1855
1855
  * Built by freeday-timepicker.js from an empty [data-fdy-timepicker] wrapper.
1856
1856
  * Trigger visual mirrors the date picker for a consistent control family. */
1857
1857
  .fdy-timepicker{position:relative;display:inline-block;width:100%;max-width:11rem;}
@@ -1875,14 +1875,14 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1875
1875
  .fdy-timepicker__opt[aria-selected="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
1876
1876
  @media (prefers-reduced-motion:no-preference){.fdy-timepicker__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
1877
1877
 
1878
- /* Freeday Toast (transient notification) */
1878
+ /* Freeday. Toast (transient notification) */
1879
1879
  /* The region is a POPOVER, not merely a high z-index.
1880
- `.fdy-modal` is a native <dialog> opened with showModal(), which puts it in the TOP LAYER a
1880
+ `.fdy-modal` is a native <dialog> opened with showModal(), which puts it in the TOP LAYER, a
1881
1881
  layer above the whole z-index universe, so no number here could ever beat it. A toast raised by
1882
1882
  an action taken inside a modal was painted behind that modal's backdrop: dimmed, unreadable, and
1883
1883
  most of it under the dialog itself. `freeday-toast.js` calls showPopover() as each toast is
1884
1884
  added, which puts the region in the top layer too, and last-in wins there.
1885
- Everything after `pointer-events` is undoing the UA's [popover] defaults it arrives with
1885
+ Everything after `pointer-events` is undoing the UA's [popover] defaults, it arrives with
1886
1886
  inset:0, auto margins, a border, padding, a Canvas background and overflow:auto, none of which a
1887
1887
  toast stack wants. Where showPopover() is unavailable the element is still an ordinary fixed
1888
1888
  block at z-index 200, which is exactly what it was before. */
@@ -1905,14 +1905,14 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1905
1905
  @media (prefers-reduced-motion:no-preference){.fdy-toast{animation:fdy-toast-in var(--dur-base) var(--ease-spring);}}
1906
1906
  @keyframes fdy-toast-in{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:none;}}
1907
1907
 
1908
- /* Freeday Tooltip (CSS-only, hover/focus) */
1908
+ /* Freeday. Tooltip (CSS-only, hover/focus) */
1909
1909
  .fdy-tooltip-wrap{position:relative;display:inline-flex;}
1910
1910
  .fdy-tooltip{position:absolute;bottom:calc(100% + var(--space-2));left:50%;z-index:150;max-width:16rem;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);font-size:var(--text-xs);font-weight:var(--weight-medium);line-height:var(--leading-snug);color:var(--color-inverse-text);background:var(--color-inverse-surface);box-shadow:var(--shadow-2);opacity:0;visibility:hidden;pointer-events:none;transform:translateX(-50%) translateY(4px);transition:opacity var(--dur-fast) var(--ease-standard),transform var(--dur-fast) var(--ease-standard);}
1911
1911
  .fdy-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);border:.3rem solid transparent;border-top-color:var(--color-inverse-surface);}
1912
1912
  .fdy-tooltip-wrap:hover .fdy-tooltip,.fdy-tooltip-wrap:focus-within .fdy-tooltip{opacity:1;visibility:visible;transform:translateX(-50%) translateY(0);}
1913
1913
  @media (prefers-reduced-motion:reduce){.fdy-tooltip{transition:none;transform:translateX(-50%);}}
1914
1914
 
1915
- /* Freeday Tree view (hierarchy via nested native <details>: keyboard-toggle, zero-JS).
1915
+ /* Freeday. Tree view (hierarchy via nested native <details>: keyboard-toggle, zero-JS).
1916
1916
  * Branches expand/collapse; leaves are plain rows. Chevron + optional folder/file icons. */
1917
1917
  .fdy-tree{list-style:none;margin:0;padding:0;font-size:var(--text-sm);}
1918
1918
  .fdy-tree ul{list-style:none;margin:0;padding-left:var(--space-4);}
@@ -1929,7 +1929,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1929
1929
  /* Disabled row (branch summary or leaf): dimmed, no hover, not interactive. */
1930
1930
  .fdy-tree__leaf[aria-disabled="true"],.fdy-tree__branch[aria-disabled="true"] > summary{opacity:.5;cursor:not-allowed;pointer-events:none;}
1931
1931
 
1932
- /* Checkbox variant a selectable tree (cascading parent/child via freeday-tree.js).
1932
+ /* Checkbox variant, a selectable tree (cascading parent/child via freeday-tree.js).
1933
1933
  * A native <input class="fdy-checkbox"> sits at the start of every row; a partially
1934
1934
  * selected branch shows the native :indeterminate dash. The checkbox stops click
1935
1935
  * propagation, so toggling selection never expands/collapses the branch. */