@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
package/dist/freeday.css CHANGED
@@ -1,5 +1,5 @@
1
- /* Freeday components GENERATED by tokens/build.mjs. Do not edit by hand. */
2
- /* Freeday base layer reset ringan + default via token */
1
+ /* Freeday components. GENERATED by tokens/build.mjs. Do not edit by hand. */
2
+ /* Freeday base layer: reset ringan + default via token */
3
3
  *, *::before, *::after { box-sizing: border-box; }
4
4
  html { -webkit-text-size-adjust: 100%; }
5
5
  body {
@@ -17,21 +17,21 @@ a { color: var(--color-primary); }
17
17
  :where(button, input, select, textarea) { font: inherit; }
18
18
  /* Hidden from sight, kept for assistive tech. `clip` hides PAINTING, not LAYOUT: the box still
19
19
  * has a position, and because it is position:absolute its containing block is the nearest
20
- * POSITIONED ancestor with none, the initial containing block (the document itself).
20
+ * POSITIONED ancestor, and with none, the initial containing block (the document itself).
21
21
  *
22
22
  * That matters, because `overflow` only clips a descendant whose containing block is inside the
23
23
  * overflow box. So a hidden span in a horizontally scrolling table (the kit's own recommended way
24
- * to name an icon button) parks at its static position possibly thousands of px to the right —
24
+ * to name an icon button) parks at its static position, possibly thousands of px to the right —
25
25
  * and drags the whole DOCUMENT sideways: invisible, and immune to `overflow-x:hidden` on every
26
26
  * wrapper. Measured before the fix: 1351px of phantom page scroll from 11 spans.
27
27
  *
28
- * The fix is not here it is on the containers: every clipping/scrolling container in the kit
28
+ * The fix is not here but on the containers: every clipping/scrolling container in the kit
29
29
  * that holds consumer markup declares `position:relative` so it becomes the containing block for
30
30
  * its own out-of-flow content (see test/css.test.mjs, which will not let a new one skip it).
31
- * Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not
31
+ * Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not,
32
32
  * the honest check is `window.scrollTo(9999, 0)` then reading `window.scrollX`. */
33
33
  .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; }
34
- /* Opt-in list reset. base.css is intentionally a *light* reset it does NOT strip list/paragraph
34
+ /* Opt-in list reset. base.css is intentionally a *light* reset: it does NOT strip list/paragraph
35
35
  * margins (see the kit's own list components, which each reset themselves). If you run a utility
36
36
  * framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
37
37
  * bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
@@ -40,10 +40,10 @@ a { color: var(--color-primary); }
40
40
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
41
41
  }
42
42
 
43
- /* Freeday Accordion (native <details>/<summary>, zero-JS) */
44
- /* position:relative containing block for out-of-flow panel content (see base.css). NOT redundant
43
+ /* Freeday. Accordion (native <details>/<summary>, zero-JS) */
44
+ /* position:relative, containing block for out-of-flow panel content (see base.css). NOT redundant
45
45
  with the panel's reveal animation: that animation gives the panel a transform (which happens to
46
- make it a containing block too), but it lives behind prefers-reduced-motion:no-preference so
46
+ make it a containing block too), but it lives behind prefers-reduced-motion:no-preference, so
47
47
  without this line the escape bug appears ONLY for readers who asked for reduced motion. */
48
48
  .fdy-accordion{position:relative;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);}
49
49
  .fdy-accordion__item + .fdy-accordion__item{border-top:var(--bw) solid var(--color-border);}
@@ -55,10 +55,10 @@ a { color: var(--color-primary); }
55
55
  .fdy-accordion__item > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
56
56
  .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);}
57
57
  /* Content reveal on open (fade + rise). Native <details> can't animate its own height cross-
58
- * browser without JS, so we animate the revealed panel instead cheap, safe, reduced-motion gated. */
58
+ * browser without JS, so we animate the revealed panel instead, cheap, safe, reduced-motion gated. */
59
59
  @media (prefers-reduced-motion:no-preference){.fdy-accordion__item[open] > .fdy-accordion__panel{animation:fdy-combo-in var(--dur-base) var(--ease-standard);}}
60
60
 
61
- /* Freeday Alert (inline) */
61
+ /* Freeday. Alert (inline) */
62
62
  .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);}
63
63
  .fdy-alert__icon{flex:none;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-top:.05rem;}
64
64
  .fdy-alert__icon svg{width:100%;height:100%;}
@@ -77,17 +77,17 @@ a { color: var(--color-primary); }
77
77
  .fdy-alert--danger{background:var(--color-danger-soft);border-color:color-mix(in srgb,var(--color-danger) 26%,transparent);}
78
78
  .fdy-alert--danger .fdy-alert__title,.fdy-alert--danger .fdy-alert__icon{color:var(--color-danger-strong);}
79
79
 
80
- /* Freeday App Shell.
80
+ /* Freeday. App Shell.
81
81
  * Natural document scroll: the shell grows with content and the PAGE scrolls,
82
82
  * while the sidebar and topbar are position:sticky so they stay in view. No
83
83
  * fixed-height / internal-scroll trickery (that leaves a white gap if the host
84
84
  * page can still scroll). Structure: .fdy-app > (.fdy-app__sidebar, .fdy-app__content
85
85
  * > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
86
- * topbar a tall containing block so it can travel a bare grid cell can't. */
86
+ * topbar a tall containing block so it can travel, a bare grid cell can't. */
87
87
  .fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
88
88
  .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);}
89
89
  .fdy-app__content{flex:1;min-width:0;display:flex;flex-direction:column;}
90
- /* Brand is a fixed header same height as the topbar so they align, and only
90
+ /* Brand is a fixed header, same height as the topbar so they align, and only
91
91
  * the nav below it scrolls. Bare text (no children) renders as one bold
92
92
  * display line via the rules on .fdy-app__brand itself. For a mark + two-line
93
93
  * title/subtitle, add structured children mirroring .fdy-appbar__brand:
@@ -103,10 +103,10 @@ a { color: var(--color-primary); }
103
103
  .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;}
104
104
  .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;}
105
105
  .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);}
106
- /* Horizontal variant the SAME nav links laid out as a row, for a top-nav application (one that
106
+ /* Horizontal variant, the SAME nav links laid out as a row, for a top-nav application (one that
107
107
  * puts its primary navigation in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar). Deliberately
108
108
  * a modifier and not a new block: the item, its states and `aria-current="page"` are unchanged. That
109
- * is the point a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
109
+ * is the point, a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
110
110
  * ARIA on an anchor) and never a tab role, so borrowing `.fdy-tabs` for routes half-adopts a contract
111
111
  * (roving tabindex, a panel per tab) that route links do not honour. position:relative because the
112
112
  * row scrolls; see base.css on why a scroller must be a containing block. */
@@ -118,7 +118,7 @@ a { color: var(--color-primary); }
118
118
  .fdy-nav__item[aria-current="page"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
119
119
  .fdy-nav__item:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
120
120
  /* Sidebar-menu extras: per-node icon (leading), count badge (trailing), and a
121
- * text label that truncates. Any of these are optional a bare text item works. */
121
+ * text label that truncates. Any of these are optional, a bare text item works. */
122
122
  .fdy-nav__icon{flex:none;display:inline-flex;width:1.15rem;height:1.15rem;}
123
123
  .fdy-nav__icon svg{display:block;width:100%;height:100%;}
124
124
  .fdy-nav__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
@@ -128,7 +128,7 @@ a { color: var(--color-primary); }
128
128
  .fdy-nav__group{margin:0;}
129
129
  .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);}
130
130
  /* Prose, and a control name: this is the <summary> of a nav group, at --text-xs uppercase. It is
131
- the second class found spending the decorative ink on type (after .fdy-eyebrow) and it was
131
+ the second class found spending the decorative ink on type (after .fdy-eyebrow), and it was
132
132
  already brightening to --color-text-muted on hover, which is the rule admitting its resting
133
133
  state was too faint to read. */
134
134
  .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);}
@@ -137,7 +137,7 @@ a { color: var(--color-primary); }
137
137
  .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);}
138
138
  .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;}
139
139
  .fdy-nav__group[open] > .fdy-nav__grouplabel::after{transform:rotate(45deg);}
140
- /* --flat: calmer, non-collapsible grouped nav hide the caret, drop the
140
+ /* --flat: calmer, non-collapsible grouped nav, hide the caret, drop the
141
141
  * inter-group divider and the collapse cursor. Works on native <details> groups
142
142
  * (caret just hidden) and on static <div>/<p> groups (never collapsible). */
143
143
  .fdy-nav--flat .fdy-nav__grouplabel::after{display:none;}
@@ -150,7 +150,7 @@ a { color: var(--color-primary); }
150
150
  .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;}
151
151
  .fdy-nav__tree[open] > summary .fdy-nav__caret{transform:rotate(45deg);}
152
152
  .fdy-nav__sub{display:flex;flex-direction:column;gap:2px;padding-left:calc(var(--space-3) + 1.15rem);margin-top:2px;}
153
- /* Collapse toggle (hamburger) sits in the topbar; collapses the sidebar. */
153
+ /* Collapse toggle (hamburger), sits in the topbar; collapses the sidebar. */
154
154
  .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);}
155
155
  .fdy-app__navtoggle:hover{background:var(--color-surface-2);color:var(--color-text);}
156
156
  .fdy-app__navtoggle:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
@@ -163,7 +163,7 @@ a { color: var(--color-primary); }
163
163
  .fdy-app__main{flex:1;padding:var(--space-8);}
164
164
  .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);}
165
165
  .fdy-skip:focus{transform:translateY(0);}
166
- /* --static: for a bounded/embedded shell (a demo box, not the whole page)
166
+ /* --static: for a bounded/embedded shell (a demo box, not the whole page),
167
167
  * drop sticky so nothing escapes the box; sidebar stretches to fill it. */
168
168
  .fdy-app--static .fdy-app__sidebar{position:static;height:auto;align-self:stretch;}
169
169
  .fdy-app--static .fdy-app__topbar{position:static;}
@@ -179,7 +179,7 @@ a { color: var(--color-primary); }
179
179
  }
180
180
  @media (prefers-reduced-motion:reduce){.fdy-app__sidebar{transition:none;}}
181
181
 
182
- /* Freeday App bar (standalone top bar: brand · actions · spacer).
182
+ /* Freeday. App bar (standalone top bar: brand · actions · spacer).
183
183
  * Distinct from .fdy-app__topbar (which is bound to the shell grid). Use anywhere;
184
184
  * sticky / elevated / dense / coloured via modifiers. Pure CSS. */
185
185
  .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);}
@@ -192,19 +192,19 @@ a { color: var(--color-primary); }
192
192
  .fdy-appbar__spacer{flex:1;}
193
193
  .fdy-appbar__actions{display:inline-flex;align-items:center;gap:var(--space-1);}
194
194
  /* Primary navigation inside the bar: `<nav class="fdy-nav fdy-nav--horizontal">` with
195
- `.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own a nav link
195
+ `.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own, a nav link
196
196
  is the same component whether the nav is a sidebar column or a top row. */
197
197
  /* Controls on a coloured app bar go on-colour (icons stay legible, hover is a light wash) */
198
198
  .fdy-appbar--primary .fdy-btn--ghost{background:transparent;border-color:transparent;color:var(--color-on-primary);box-shadow:none;}
199
199
  .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;}
200
200
  /* Nav links on a coloured bar: full on-colour ink in every state (a dimmed variant would trade away
201
201
  the contrast the bar's own token pair guarantees). The current page is carried by the background
202
- wash plus the semibold weight the base rule already applies and by `aria-current` itself. */
202
+ wash plus the semibold weight the base rule already applies, and by `aria-current` itself. */
203
203
  .fdy-appbar--primary .fdy-nav__item{color:var(--color-on-primary);}
204
204
  .fdy-appbar--primary .fdy-nav__item:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);}
205
205
  .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);}
206
206
 
207
- /* Freeday Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
207
+ /* Freeday. Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
208
208
  * Enhanced by freeday-autocomplete.js. */
209
209
  .fdy-autocomplete{position:relative;display:block;max-width:22rem;}
210
210
  .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);}
@@ -217,20 +217,20 @@ a { color: var(--color-primary); }
217
217
  .fdy-autocomplete__empty[hidden]{display:none;}
218
218
  @media (prefers-reduced-motion:no-preference){.fdy-autocomplete__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
219
219
 
220
- /* Freeday Avatar */
220
+ /* Freeday. Avatar */
221
221
  .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;}
222
222
  .fdy-avatar img{width:100%;height:100%;object-fit:cover;}
223
223
  /* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
224
224
  `--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
225
225
  crosses the circle. 1.5rem leaves the same ~4px breathing room the kit's controls give their
226
- icons. Uses `--text-xs` (the smallest type token the kit has there is no `2xs`). */
226
+ icons. Uses `--text-xs` (the smallest type token the kit has, there is no `2xs`). */
227
227
  .fdy-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--text-xs);}
228
228
  .fdy-avatar--sm{width:2rem;height:2rem;font-size:var(--text-xs);}
229
229
  .fdy-avatar--lg{width:3.5rem;height:3.5rem;font-size:var(--text-lg);}
230
230
  .fdy-avatar-group{display:inline-flex;}
231
231
  .fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
232
232
  .fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
233
- /* Decorative identity tones a soft tint of a categorical chart colour with a theme-aware,
233
+ /* Decorative identity tones, a soft tint of a categorical chart colour with a theme-aware,
234
234
  * text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
235
235
  * test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
236
236
  * hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
@@ -244,7 +244,7 @@ a { color: var(--color-primary); }
244
244
  .fdy-avatar--tone-7{--_fdy-avatar-tone:var(--tone-7);}
245
245
  .fdy-avatar--tone-8{--_fdy-avatar-tone:var(--tone-8);}
246
246
 
247
- /* Freeday Badge */
247
+ /* Freeday. Badge */
248
248
  .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);}
249
249
  .fdy-badge--success{color:var(--color-success-strong);background:var(--color-success-soft);border-color:color-mix(in srgb,var(--color-success) 26%,transparent);}
250
250
  .fdy-badge--warning{color:var(--color-warning-strong);background:var(--color-warning-soft);border-color:color-mix(in srgb,var(--color-warning) 26%,transparent);}
@@ -252,12 +252,12 @@ a { color: var(--color-primary); }
252
252
  .fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
253
253
  .fdy-badge--outline{background:transparent;border-color:var(--color-border-strong);color:var(--color-text-muted);box-shadow:none;}
254
254
 
255
- /* Categorical tone badges a status vocabulary larger than the semantic palette, from the
255
+ /* Categorical tone badges, a status vocabulary larger than the semantic palette, from the
256
256
  * general --tone-1..8 tokens. Same validated AA formula as .fdy-avatar--tone-* and
257
257
  * .fdy-chip--tone-* (text >=4.5:1 both themes, gated by test/contrast.test.mjs).
258
258
  *
259
259
  * Semantics come first: a state that IS good, bad or waiting takes --success / --danger /
260
- * --warning. These are for the rest the states a workflow distinguishes but a palette of five
260
+ * --warning. These are for the rest, the states a workflow distinguishes but a palette of five
261
261
  * cannot. A back-office document list carrying Draft, Submitted, Approved, Completed, Settled,
262
262
  * Closed, Transferred, InDeclaration, Open and Rejected in ONE column collapsed to three colours,
263
263
  * which is the report this shipped for. */
@@ -271,7 +271,7 @@ a { color: var(--color-primary); }
271
271
  .fdy-badge--tone-7{--_fdy-badge-tone:var(--tone-7);}
272
272
  .fdy-badge--tone-8{--_fdy-badge-tone:var(--tone-8);}
273
273
 
274
- /* Overlay badge anchors a small count/dot/icon badge to a corner of any element
274
+ /* Overlay badge, anchors a small count/dot/icon badge to a corner of any element
275
275
  * (icon, button, text). Wrap the content in .fdy-badge-wrap; add .fdy-badge-ov as a
276
276
  * sibling. Default is a solid neutral pill; colour + dot + position via modifiers. */
277
277
  .fdy-badge-wrap{position:relative;display:inline-flex;vertical-align:middle;}
@@ -281,7 +281,7 @@ a { color: var(--color-primary); }
281
281
  /* Solid colours safe for count/text (on-* tokens, AA in light & dark) */
282
282
  .fdy-badge-ov--primary{background:var(--color-primary);color:var(--color-on-primary);}
283
283
  .fdy-badge-ov--danger{background:var(--color-danger-btn);color:var(--color-on-danger);}
284
- /* Dot-oriented accents (for dots plain text on these is not guaranteed AA) */
284
+ /* Dot-oriented accents (for dots, plain text on these is not guaranteed AA) */
285
285
  .fdy-badge-ov--accent{background:var(--color-accent);}
286
286
  .fdy-badge-ov--info{background:var(--color-info);}
287
287
  .fdy-badge-ov--success{background:var(--color-success);}
@@ -291,7 +291,7 @@ a { color: var(--color-primary); }
291
291
  .fdy-badge-ov--bottom-right{top:auto;bottom:0;transform:translate(50%,50%);}
292
292
  .fdy-badge-ov--bottom-left{top:auto;bottom:0;right:auto;left:0;transform:translate(-50%,50%);}
293
293
 
294
- /* Freeday Breadcrumb */
294
+ /* Freeday. Breadcrumb */
295
295
  .fdy-breadcrumb{font-size:var(--text-sm);}
296
296
  .fdy-breadcrumb__list{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);list-style:none;margin:0;padding:0;}
297
297
  .fdy-breadcrumb__item{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--color-text-muted);}
@@ -301,23 +301,23 @@ a { color: var(--color-primary); }
301
301
  .fdy-breadcrumb__item[aria-current="page"]{color:var(--color-text);font-weight:var(--weight-medium);}
302
302
  .fdy-breadcrumb__sep{color:var(--color-text-subtle);user-select:none;}
303
303
 
304
- /* Freeday Responsive display utilities (pairs with freeday-breakpoint.js).
304
+ /* Freeday. Responsive display utilities (pairs with freeday-breakpoint.js).
305
305
  * Breakpoint scale (min-width, px): sm 600 · md 960 · lg 1280 · xl 1920.
306
- * NOTE: media-query breakpoints must be literal CSS can't read custom properties
306
+ * NOTE: media-query breakpoints must be literal. CSS can't read custom properties
307
307
  * inside @media, so these px are structural, not stray magic numbers. */
308
308
  .fdy-hidden{display:none !important;}
309
309
 
310
- /* hide-below-X hidden when the viewport is narrower than X (content shows at X and up) */
310
+ /* hide-below-X, hidden when the viewport is narrower than X (content shows at X and up) */
311
311
  @media (max-width:599.98px){.fdy-hide-below-sm{display:none !important;}}
312
312
  @media (max-width:959.98px){.fdy-hide-below-md{display:none !important;}}
313
313
  @media (max-width:1279.98px){.fdy-hide-below-lg{display:none !important;}}
314
314
 
315
- /* hide-above-X hidden when the viewport is X and wider (content shows below X) */
315
+ /* hide-above-X, hidden when the viewport is X and wider (content shows below X) */
316
316
  @media (min-width:600px){.fdy-hide-above-sm{display:none !important;}}
317
317
  @media (min-width:960px){.fdy-hide-above-md{display:none !important;}}
318
318
  @media (min-width:1280px){.fdy-hide-above-lg{display:none !important;}}
319
319
 
320
- /* Freeday Button */
320
+ /* Freeday. Button */
321
321
  .fdy-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
322
322
  height:var(--control-h);padding:0 var(--space-4);
323
323
  font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-semibold);
@@ -330,7 +330,7 @@ a { color: var(--color-primary); }
330
330
  .fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px color-mix(in srgb,var(--color-primary) 46%,transparent);}
331
331
  .fdy-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none;}
332
332
  /* Pressed / toggle. `aria-pressed` is already the right attribute for a toggle button, so this adds
333
- no class and no new markup contract and it is what makes .fdy-btn-group a complete segmented
333
+ no class and no new markup contract, and it is what makes .fdy-btn-group a complete segmented
334
334
  control: the group joins the borders, this says which segment you are on. Each fill variant
335
335
  defines its OWN pressed look below; a single shared rule cannot work, because a gradient is a
336
336
  background-IMAGE and would blank the ghost variant's background-color. Solid: invert the gradient
@@ -342,12 +342,12 @@ a { color: var(--color-primary); }
342
342
  .fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
343
343
  .fdy-btn--ghost:active{transform:translateY(1px);}
344
344
 
345
- /* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger` —
346
- which every reader parses as "quiet destructive" rendered a second SOLID button beside Save.
345
+ /* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger`,
346
+ which every reader parses as "quiet destructive", rendered a second SOLID button beside Save.
347
347
  That is a hierarchy defect, not a cosmetic one: the base class is already the one primary per
348
348
  screen. `.fdy-menu__item--danger` has always modelled the quiet form (red text, not a red block);
349
349
  the pairing rules below bring it to the button. They are more specific than this one and come
350
- after it, so this rule needs no :not() guard of its own one mechanism, not two. */
350
+ after it, so this rule needs no :not() guard of its own, one mechanism, not two. */
351
351
  .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);}
352
352
  .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);}
353
353
  .fdy-btn--danger:active{transform:translateY(1px);}
@@ -361,21 +361,21 @@ a { color: var(--color-primary); }
361
361
  .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);}
362
362
  .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);}
363
363
 
364
- /* Icon inside a button scales with the button's font size (works for text+icon & icon-only) */
364
+ /* Icon inside a button, scales with the button's font size (works for text+icon & icon-only) */
365
365
  .fdy-btn__icon{display:inline-flex;flex:none;}
366
366
  .fdy-btn__icon svg{display:block;width:1.15em;height:1.15em;}
367
367
 
368
- /* Text button lowest emphasis (no fill/border), colored label, subtle hover tint */
368
+ /* Text button, lowest emphasis (no fill/border), colored label, subtle hover tint */
369
369
  .fdy-btn--text{background:none;color:var(--color-primary-strong);border-color:transparent;box-shadow:none;}
370
370
  .fdy-btn--text:hover{transform:none;background:var(--color-primary-soft);box-shadow:none;}
371
371
  .fdy-btn--text:active{transform:translateY(1px);box-shadow:none;}
372
- /* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink) the same pairing
372
+ /* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink), the same pairing
373
373
  .fdy-nav__item uses for the current page, and one the AA contrast gate already covers. This is
374
374
  the realistic segmented control: a row of --ghost buttons with exactly one aria-pressed. */
375
375
  .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;}
376
376
  .fdy-btn--ghost[aria-pressed="true"]:hover,.fdy-btn--text[aria-pressed="true"]:hover{background:var(--color-primary-soft);transform:none;box-shadow:none;}
377
377
 
378
- /* Stretched hit area (#007) spread this button's click target over its nearest positioned
378
+ /* Stretched hit area (#007), spread this button's click target over its nearest positioned
379
379
  * ancestor, so "one card, one primary action, one escape hatch" needs no click handler on the
380
380
  * <article>: the real control stays a real <button>/<a> and keeps its keyboard semantics.
381
381
  * `.fdy-card` is already position:relative, so it is the anchor.
@@ -384,19 +384,19 @@ a { color: var(--color-primary); }
384
384
  * a transformed element becomes the containing block for its own absolutely-positioned descendants,
385
385
  * so `inset:0` re-anchors from the card to the button's own box the moment the button is hovered or
386
386
  * pressed. Measured: mousedown lands on the button, the overlay collapses under the pointer, mouseup
387
- * lands on some other element, and the browser fires `click` on their common ancestor the button
387
+ * lands on some other element, and the browser fires `click` on their common ancestor, the button
388
388
  * never gets one. It demos fine on hover for --text/--ghost (their hover is already transform:none)
389
389
  * and only a real press reveals it; the default and --danger fills break one step earlier, on hover.
390
390
  * Press feedback moves to the card, which is the object actually being pressed (see card.css). */
391
391
  .fdy-btn--stretch::after{content:'';position:absolute;inset:0;}
392
392
  .fdy-btn--stretch:hover,.fdy-btn--stretch:active{transform:none;}
393
393
 
394
- /* Icon-only square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
394
+ /* Icon-only, square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
395
395
  .fdy-btn--icon{width:var(--control-h);padding:0;}
396
396
  .fdy-btn--icon.fdy-btn--sm{width:calc(var(--control-h) - var(--space-2));}
397
397
  .fdy-btn--icon.fdy-btn--lg{width:calc(var(--control-h) + var(--space-2));}
398
398
 
399
- /* Button group segmented, joined borders, rounded only at the ends */
399
+ /* Button group, segmented, joined borders, rounded only at the ends */
400
400
  .fdy-btn-group{display:inline-flex;}
401
401
  .fdy-btn-group .fdy-btn{border-radius:0;}
402
402
  .fdy-btn-group .fdy-btn:not(:first-child){margin-left:calc(var(--bw) * -1);}
@@ -404,7 +404,7 @@ a { color: var(--color-primary); }
404
404
  .fdy-btn-group .fdy-btn:first-child{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md);}
405
405
  .fdy-btn-group .fdy-btn:last-child{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md);}
406
406
 
407
- /* FAB circular elevated action button (icon), optional extended pill with label */
407
+ /* FAB, circular elevated action button (icon), optional extended pill with label */
408
408
  .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);}
409
409
  .fdy-fab:hover{transform:translateY(-1px);box-shadow:var(--shadow-4);}
410
410
  .fdy-fab:active{transform:translateY(1px);}
@@ -417,35 +417,35 @@ a { color: var(--color-primary); }
417
417
  .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);}
418
418
  .fdy-fab--accent{background:linear-gradient(180deg,var(--color-accent),color-mix(in srgb,#000 10%,var(--color-accent)));color:var(--color-on-accent);}
419
419
 
420
- /* Freeday Card */
421
- /* position:relative containing block for out-of-flow card content (see base.css); it also gives
420
+ /* Freeday. Card */
421
+ /* position:relative, containing block for out-of-flow card content (see base.css); it also gives
422
422
  consumers the anchor a corner ribbon/badge needs. */
423
423
  .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);}
424
424
  .fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
425
425
  .fdy-card--interactive{cursor:pointer;}
426
426
  .fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
427
- /* Pressing a stretched target (see .fdy-btn--stretch) is pressing the CARD the button's own
427
+ /* Pressing a stretched target (see .fdy-btn--stretch) is pressing the CARD, the button's own
428
428
  nudge had to go, and the object under the pointer is the card. Ordered before the reduced-motion
429
429
  block and the disabled rules so both still win at equal specificity. */
430
430
  .fdy-card--interactive:has(.fdy-btn--stretch:active){transform:translateY(-1px);}
431
431
  /* The global reduced-motion reset kills the ANIMATION; drop the -3px shift too so the card
432
432
  * doesn't jump. The shadow still communicates the affordance. */
433
433
  @media (prefers-reduced-motion:reduce){.fdy-card--interactive:hover,.fdy-card--interactive:has(.fdy-btn--stretch:active){transform:none;}}
434
- /* When the whole card IS the control a real <button> so the row is keyboard-reachable and
435
- * announced reset the UA button box. Note: it does NOT reset background/border, because
434
+ /* When the whole card IS the control, a real <button> so the row is keyboard-reachable and
435
+ * announced, reset the UA button box. Note: it does NOT reset background/border, because
436
436
  * .fdy-card already sets its own surface + border (resetting them here would strip the card). */
437
437
  .fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
438
- /* Disabled card-as-control same contract as .fdy-list__row--button: dim + not-allowed, and the
438
+ /* Disabled card-as-control, same contract as .fdy-list__row--button: dim + not-allowed, and the
439
439
  --interactive lift is withdrawn so a refusing control stops answering the pointer. */
440
440
  .fdy-card--button:disabled,.fdy-card--button[aria-disabled="true"],.fdy-card--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
441
441
  .fdy-card--interactive:disabled:hover,.fdy-card--interactive[aria-disabled="true"]:hover{box-shadow:var(--shadow-lift);transform:none;}
442
- /* Escape hatches stay clickable above a stretched target automatic, not a documented convention,
442
+ /* Escape hatches stay clickable above a stretched target, automatic, not a documented convention,
443
443
  because forgetting it fails SILENTLY: the secondary control looks and hovers exactly as before and
444
444
  simply never receives the click, which is the very symptom the pattern exists to cure. Matched by
445
445
  shape (anything focusable) so a control the kit does not ship yet is covered too. */
446
446
  :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;}
447
447
  /* The position is a DEFAULT, not an override: z-index needs a positioned box (or a flex/grid item),
448
- but an app that deliberately pins a control inside the card a corner dismiss, a favourite star
448
+ but an app that deliberately pins a control inside the card, a corner dismiss, a favourite star,
449
449
  must keep its own `position:absolute`. Wrapped in :where() so this whole selector weighs nothing
450
450
  and ANY app rule wins it; measured, because at normal specificity the kit silently dragged an
451
451
  absolutely positioned dismiss button back into the flow. Do not merge these two rules. */
@@ -455,9 +455,9 @@ a { color: var(--color-primary); }
455
455
  .fdy-card__desc{font-size:var(--text-sm);color:var(--color-text-muted);margin:0;line-height:var(--leading-normal);}
456
456
  .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;}
457
457
 
458
- /* Freeday Carousel (scroll-snap track + arrows + dots). Enhanced by freeday-carousel.js. */
458
+ /* Freeday. Carousel (scroll-snap track + arrows + dots). Enhanced by freeday-carousel.js. */
459
459
  .fdy-carousel{position:relative;}
460
- /* position:relative the arrows are positioned against .fdy-carousel (the parent), so this only
460
+ /* position:relative means the arrows are positioned against .fdy-carousel (the parent), so this only
461
461
  catches out-of-flow content inside the SLIDES, which would otherwise escape the viewport
462
462
  entirely and scroll the page by one slide-offset per slide (see base.css). */
463
463
  .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;}
@@ -476,10 +476,10 @@ a { color: var(--color-primary); }
476
476
  .fdy-carousel__dot:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 40%,transparent);}
477
477
  @media (prefers-reduced-motion:reduce){.fdy-carousel__viewport{scroll-behavior:auto;}}
478
478
 
479
- /* Freeday Cascade select (hierarchical drill-down picker).
479
+ /* Freeday. Cascade select (hierarchical drill-down picker).
480
480
  * Built by freeday-cascade.js from a [data-fdy-cascade] wrapper whose hidden
481
481
  * nested <ul> is the data model: a <li> with a child <ul> is a branch, one
482
- * without is a selectable leaf. The popover shows one level at a time branches
482
+ * without is a selectable leaf. The popover shows one level at a time, branches
483
483
  * drill in, a back control ascends, a leaf selects (value = full path).
484
484
  * Trigger visual matches the combo / date / time control family. */
485
485
  .fdy-cascade{position:relative;display:inline-block;width:100%;max-width:22rem;}
@@ -516,16 +516,16 @@ a { color: var(--color-primary); }
516
516
  .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);}
517
517
  @media (prefers-reduced-motion:no-preference){.fdy-cascade__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
518
518
 
519
- /* Freeday Choose-from-list dialog (searchable master-data picker).
519
+ /* Freeday. Choose-from-list dialog (searchable master-data picker).
520
520
  * Built on .fdy-modal: header + search + scrollable results table + footer.
521
521
  * Backs a choose-from-list field (readonly input + .fdy-input-group__btn magnifier). */
522
522
  .fdy-modal--cfl{width:min(46rem,calc(100vw - var(--space-8)));}
523
523
  .fdy-modal--cfl .fdy-modal__body{padding:0;display:flex;flex-direction:column;min-height:0;overflow:hidden;}
524
524
 
525
- /* Search bar single leading-icon field; Enter/typing filters the results. */
525
+ /* Search bar, single leading-icon field; Enter/typing filters the results. */
526
526
  .fdy-cfl__search{flex:none;padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
527
527
 
528
- /* Results dense rows, sticky header, its own scroll region. */
528
+ /* Results, dense rows, sticky header, its own scroll region. */
529
529
  .fdy-cfl__results{overflow:auto;max-height:min(24rem,50vh);}
530
530
  .fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
531
531
  .fdy-cfl__row{cursor:pointer;}
@@ -540,12 +540,12 @@ a { color: var(--color-primary); }
540
540
  .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;}
541
541
  .fdy-cfl__empty[hidden]{display:none;}
542
542
 
543
- /* Footer selection count (multi) sits left, actions right. */
543
+ /* Footer, selection count (multi) sits left, actions right. */
544
544
  .fdy-modal--cfl .fdy-modal__footer{justify-content:space-between;}
545
545
  .fdy-cfl__count{font-size:var(--text-sm);color:var(--color-text-muted);}
546
546
  .fdy-cfl__actions{display:flex;gap:var(--space-2);}
547
547
 
548
- /* Freeday Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
548
+ /* Freeday. Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
549
549
  * Colour comes from --fdy-chart (a token, set inline by the renderer from data-fdy-color). */
550
550
 
551
551
  /* Sparkline / trend line (SVG) */
@@ -565,7 +565,7 @@ a { color: var(--color-primary); }
565
565
  /* Hover emphasis: dim all bars while the chart is hovered, keep the hovered one lit. */
566
566
  @media (hover:hover){.fdy-bars:hover .fdy-bars__bar{opacity:.4;}.fdy-bars__col:hover .fdy-bars__bar{opacity:1;}}
567
567
 
568
- /* Interactive tooltip (shared by bar + donut) Chart.js-style hover readout. */
568
+ /* Interactive tooltip (shared by bar + donut). Chart.js-style hover readout. */
569
569
  .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);}
570
570
  .fdy-chart__tip.is-visible{opacity:1;}
571
571
 
@@ -586,7 +586,7 @@ a { color: var(--color-primary); }
586
586
  /* Horizontal legend for cartesian charts (legend sits above the plot). */
587
587
  .fdy-chart__legend--row{flex-direction:row;flex-wrap:wrap;gap:var(--space-2) var(--space-4);}
588
588
 
589
- /* Cartesian charts (line / area / multi-series & stacked bar) SVG with themed axes.
589
+ /* Cartesian charts (line / area / multi-series & stacked bar). SVG with themed axes.
590
590
  * Axis colour comes from --chart-grid / --chart-tick (both theme-aware); series colours are
591
591
  * set inline by the renderer from --chart-1..8, so the whole chart recolours on data-theme. */
592
592
  .fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100%;--fdy-chart-tick-size:var(--text-xs);}
@@ -601,10 +601,10 @@ a { color: var(--color-primary); }
601
601
  .fdy-chart-xy__bar{transition:opacity var(--dur-fast) var(--ease-standard);}
602
602
  .fdy-chart-xy__band{fill:transparent;cursor:pointer;}
603
603
 
604
- /* Freeday Chip (tag / removable filter) */
604
+ /* Freeday. Chip (tag / removable filter) */
605
605
  .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);}
606
606
  .fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:color-mix(in srgb,var(--color-primary) 22%,transparent);}
607
- /* Categorical tone chips non-semantic category tags / legend swatches, from the general
607
+ /* Categorical tone chips, non-semantic category tags / legend swatches, from the general
608
608
  * --tone-1..8 palette. Same validated AA formula as .fdy-avatar--tone-* (text >=4.5:1 both themes,
609
609
  * gated by test/contrast.test.mjs). */
610
610
  .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));}
@@ -632,14 +632,14 @@ a { color: var(--color-primary); }
632
632
  .fdy-chip__check svg{display:block;width:100%;height:100%;}
633
633
  .fdy-chip--filter[aria-pressed="true"] .fdy-chip__check{display:inline-flex;}
634
634
 
635
- /* Freeday Combobox (custom styled select, WAI-ARIA APG select-only) */
635
+ /* Freeday. Combobox (custom styled select, WAI-ARIA APG select-only) */
636
636
  .fdy-combo{position:relative;display:block;max-width:22rem;}
637
637
  .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);}
638
638
  .fdy-combo__button:hover{border-color:var(--color-text-muted);}
639
639
  .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);}
640
640
  .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);}
641
641
  .fdy-combo__button:disabled,.fdy-combo__button[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
642
- /* Read-only: stays focusable & shows its value at full contrast but can't open muted surface,
642
+ /* Read-only: stays focusable & shows its value at full contrast but can't open, muted surface,
643
643
  softened border, default cursor, no focus-ring escalation (matches input[readonly], #39). */
644
644
  .fdy-combo__button[aria-readonly="true"]{background:var(--color-surface-2);border-color:var(--color-border);cursor:default;}
645
645
  .fdy-combo__button[aria-readonly="true"]:hover{border-color:var(--color-border);}
@@ -663,7 +663,7 @@ a { color: var(--color-primary); }
663
663
  .fdy-combo__option.is-highlighted[aria-selected="true"]{box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 55%,transparent);}
664
664
  /* The tick is CSS, not content (#004 §2). Inside role="option" it joined the option's ACCESSIBLE
665
665
  NAME, so the selected option announced "✓August" while every other announced "August": the state
666
- was read twice once as aria-selected, once as decoration and a test asking for an option by
666
+ was read twice, once as aria-selected, once as decoration, and a test asking for an option by
667
667
  its own label stopped matching the one that was selected. The `/ ""` is alt text: the glyph is
668
668
  painted, the accessibility tree gets nothing. Same reasoning as .fdy-label--required (1.29.0).
669
669
  The empty box still reserves its width, so labels stay aligned whether selected or not. */
@@ -672,11 +672,11 @@ a { color: var(--color-primary); }
672
672
  @media (prefers-reduced-motion:no-preference){.fdy-combo__listbox:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
673
673
  @keyframes fdy-combo-in{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:none;}}
674
674
 
675
- /* Freeday Page composition & type roles.
675
+ /* Freeday. Page composition & type roles.
676
676
  *
677
677
  * The kit stops at the component; these primitives encode *how a page is assembled* so screens
678
678
  * built by different people cohere. They carry the usage doctrine (see USAGE.md) in markup: a
679
- * named type role gets used, a doc gets skimmed. Everything here is token-driven layout no new
679
+ * named type role gets used, a doc gets skimmed. Everything here is token-driven layout, no new
680
680
  * colours, no new shadows. Pair with .fdy-app (the shell) and the components inside.
681
681
  */
682
682
 
@@ -685,7 +685,7 @@ a { color: var(--color-primary); }
685
685
  * page title (the mistake that collapses three levels into one grey mush). */
686
686
  /* --color-text-muted, not -subtle: an eyebrow is a small label naming the thing below it, i.e.
687
687
  prose. -subtle is gated at 3.0 on purpose (placeholders, separators, decorative glyphs) and
688
- measures 4.41 on the page ground axe reports it as a serious color-contrast failure. Same
688
+ measures 4.41 on the page ground, axe reports it as a serious color-contrast failure. Same
689
689
  argument, same ink, same 4.41 as .fdy-stat__label in 1.30.0. */
690
690
  .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;}
691
691
  .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;}
@@ -719,19 +719,19 @@ a { color: var(--color-primary); }
719
719
  .fdy-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:var(--space-5);}
720
720
  .fdy-stat{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
721
721
  /* --color-text-muted, not -subtle: this is a LABEL, i.e. text, and -subtle is deliberately gated at
722
- 3.0 (placeholders, dividers, decorative glyphs). On surface-2 -subtle measures 4.41 under AA
722
+ 3.0 (placeholders, dividers, decorative glyphs). On surface-2 -subtle measures 4.41, under AA
723
723
  for text, and a stat label is often the only thing naming the number above it. */
724
724
  .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);}
725
725
  .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;}
726
726
  .fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
727
727
  .fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
728
- /* A stat value is display type sized for a COUNT "1,284" and a back office puts money in it.
728
+ /* A stat value is display type sized for a COUNT, "1,284", and a back office puts money in it.
729
729
  "IDR 300,000.00" needs 224px at --text-3xl and the grid's own track is 11rem, so it wrapped
730
730
  between the currency and the number: two lines of 31px where the design says one. Measured, not
731
731
  guessed (#020).
732
732
  The value shrinks only when its own column is too narrow to hold it, so a wide dashboard keeps
733
733
  exactly the type it has today. Scoped to a stat INSIDE .fdy-stats, where the width comes from the
734
- grid track `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
734
+ grid track, `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
735
735
  own content. The unconditional rule above stays as the fallback wherever @container is missing. */
736
736
  @supports (container-type:inline-size){
737
737
  .fdy-stats>.fdy-stat{container-type:inline-size;}
@@ -744,7 +744,7 @@ a { color: var(--color-primary); }
744
744
  /* Icon box (#001 §4). The kit ships no icons on purpose, but every icon slot it DOES ship
745
745
  (.fdy-btn__icon, .fdy-nav__icon, .fdy-state__icon) assumes something sensible inside, so a
746
746
  standalone icon had no contract and every app invented one. 1em keeps the glyph matched to the
747
- text beside it at every scale bring your own paths, use our box. Works on the <svg> itself or
747
+ text beside it at every scale, bring your own paths, use our box. Works on the <svg> itself or
748
748
  on a wrapper around it. */
749
749
  .fdy-icon{display:inline-block;width:1em;height:1em;flex:none;vertical-align:-0.125em;}
750
750
  .fdy-icon>svg{display:block;width:100%;height:100%;}
@@ -757,13 +757,13 @@ a { color: var(--color-primary); }
757
757
  .fdy-text-warning{color:var(--color-warning-strong);}
758
758
  .fdy-text-danger{color:var(--color-danger-strong);}
759
759
 
760
- /* Freeday Date picker (input-styled trigger + calendar popover).
760
+ /* Freeday. Date picker (input-styled trigger + calendar popover).
761
761
  * Built by freeday-datepicker.js from an empty [data-fdy-datepicker] wrapper. */
762
762
  .fdy-datepicker{position:relative;display:inline-block;width:100%;max-width:14rem;}
763
763
  /* Inside a labelled field, a picker fills the field like every other control.
764
764
  The 14rem/11rem caps above suit a picker standing on its own; in a form grid
765
- they leave the date box visibly short of the combo beside it 224px against
766
- 319px in a two-column layout and the row reads as ragged. The field's own
765
+ they leave the date box visibly short of the combo beside it. 224px against
766
+ 319px in a two-column layout, and the row reads as ragged. The field's own
767
767
  max-width is what should govern there. */
768
768
  .fdy-field>.fdy-datepicker,.fdy-field>.fdy-timepicker,.fdy-field>.fdy-daterange{max-width:none;}
769
769
  .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);}
@@ -809,12 +809,12 @@ a { color: var(--color-primary); }
809
809
  .fdy-cal__nav:hover{background:var(--color-surface-2);color:var(--color-text);}
810
810
  .fdy-cal__nav:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
811
811
  .fdy-cal__grid{display:grid;grid-template-columns:repeat(7,2rem);gap:2px;}
812
- /* Month grid 3×4 over the same track width as the 7-column day grid, so the panel does not resize
812
+ /* Month grid. 3×4 over the same track width as the 7-column day grid, so the panel does not resize
813
813
  when you drill and the pointer does not have to travel.
814
814
 
815
815
  AFTER `.fdy-cal__grid`, and that is the whole rule: the element carries both classes, they weigh
816
816
  the same, so source order decides. Declared earlier, the modifier lost to the base it modifies and
817
- twelve months rendered seven across the layout the comment above described, never once applied. */
817
+ twelve months rendered seven across, the layout the comment above described, never once applied. */
818
818
  .fdy-cal__grid--months,.fdy-cal__grid--years{grid-template-columns:repeat(3,1fr);gap:var(--space-1);}
819
819
  .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;}
820
820
  .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;}
@@ -832,14 +832,14 @@ a { color: var(--color-primary); }
832
832
  /* Responsive: each picker may shrink (min-width:0) & wrap so the range never overflows a
833
833
  narrow row/phone; inline-flex still hugs to ~23rem at wider widths (look unchanged). */
834
834
  .fdy-daterange{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);}
835
- /* Each picker shrinks from an 11rem basis but not below ~7rem enough for "dd/mm/yyyy" + the icon,
835
+ /* Each picker shrinks from an 11rem basis but not below ~7rem, enough for "dd/mm/yyyy" + the icon,
836
836
  so a squeezed range compresses gracefully (and wraps) instead of clipping the date. */
837
837
  .fdy-daterange .fdy-datepicker{flex:0 1 11rem;min-width:7rem;}
838
838
  .fdy-daterange__sep{flex:none;color:var(--color-text-subtle);}
839
839
 
840
840
  @media (prefers-reduced-motion:no-preference){.fdy-datepicker__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
841
841
 
842
- /* Freeday Datetime picker (composed: date picker + time picker → one value).
842
+ /* Freeday. Datetime picker (composed: date picker + time picker → one value).
843
843
  * A thin composer (freeday-datetime.js) links a child [data-fdy-datepicker] and
844
844
  * [data-fdy-timepicker] and emits a single combined event. Icon variants come from
845
845
  * the child pickers (data-fdy-no-icon / <template data-fdy-icon>). */
@@ -848,18 +848,18 @@ a { color: var(--color-primary); }
848
848
  .fdy-datetimepicker .fdy-datepicker{width:12rem;max-width:none;}
849
849
  .fdy-datetimepicker .fdy-timepicker{width:8.5rem;max-width:none;}
850
850
 
851
- /* Freeday Description list (key-value detail view) */
851
+ /* Freeday. Description list (key-value detail view) */
852
852
  .fdy-dl{display:grid;grid-template-columns:auto 1fr;gap:var(--space-2) var(--space-5);margin:0;}
853
853
  .fdy-dl dt{color:var(--color-text-muted);font-size:var(--text-sm);}
854
854
  .fdy-dl dd{margin:0;color:var(--color-text);font-size:var(--text-sm);font-weight:var(--weight-medium);}
855
855
  .fdy-dl--rows{grid-template-columns:1fr;gap:var(--space-1);}
856
856
  .fdy-dl--rows dt{margin-top:var(--space-2);}
857
857
 
858
- /* Freeday Divider */
858
+ /* Freeday. Divider */
859
859
  .fdy-divider{border:0;border-top:var(--bw) solid var(--color-border);margin:var(--space-4) 0;}
860
860
  .fdy-divider--vertical{border-top:0;border-left:var(--bw) solid var(--color-border);margin:0 var(--space-4);align-self:stretch;}
861
861
 
862
- /* Freeday Drawer (temporary side panel on native <dialog>: focus-trap / Esc /
862
+ /* Freeday. Drawer (temporary side panel on native <dialog>: focus-trap / Esc /
863
863
  * backdrop / return-focus are free). Anchors left (default) or right.
864
864
  * Enhanced by freeday-drawer.js (open trigger + backdrop-click close). */
865
865
  /* display is gated behind [open] so the native <dialog> stays hidden until opened.
@@ -886,7 +886,7 @@ a { color: var(--color-primary); }
886
886
  .fdy-drawer[open]::backdrop{opacity:0;}
887
887
  }
888
888
 
889
- /* Freeday File upload (drop target + explicit per-file states).
889
+ /* Freeday. File upload (drop target + explicit per-file states).
890
890
  * Enhanced by freeday-upload.js; states: idle · drag-over · uploading · done · error. */
891
891
  .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);}
892
892
  .fdy-dropzone:hover{border-color:var(--color-text-muted);}
@@ -898,17 +898,17 @@ a { color: var(--color-primary); }
898
898
  .fdy-dropzone__icon svg{display:block;width:2rem;height:2rem;}
899
899
  .fdy-dropzone__title{font-size:inherit;font-weight:var(--weight-medium);color:var(--color-text);margin:0;}
900
900
  .fdy-dropzone__hint{font-size:var(--text-xs);}
901
- /* Wide (horizontal) layout icon beside the copy instead of stacked, for full-width
901
+ /* Wide (horizontal) layout, icon beside the copy instead of stacked, for full-width
902
902
  * sections. Wrap the title + hint in .fdy-dropzone__text. */
903
903
  .fdy-dropzone--row{flex-direction:row;align-items:center;justify-content:center;text-align:left;gap:var(--space-4);}
904
904
  .fdy-dropzone__text{display:flex;flex-direction:column;gap:var(--space-1);}
905
905
 
906
906
  /* File rows (list of added/uploaded/rejected files) */
907
- /* Symmetric on purpose. The list carried margin-top and nothing below, so whatever follows it
908
- an "Add files" button, in every upload UI there is sat flush against the last row: measured
907
+ /* Symmetric on purpose. The list carried margin-top and nothing below, so whatever follows it,
908
+ an "Add files" button, in every upload UI there is, sat flush against the last row: measured
909
909
  0px. A block that claims space on one side only is not spacing, it is a lean. */
910
910
  .fdy-filelist{display:flex;flex-direction:column;gap:var(--space-2);margin-top:var(--space-3);margin-bottom:var(--space-3);}
911
- /* Nothing after it, nothing to separate from so the room is not spent. */
911
+ /* Nothing after it, nothing to separate from, so the room is not spent. */
912
912
  .fdy-filelist:last-child{margin-bottom:0;}
913
913
  /* Side-by-side rows on wide screens (reflow to one column when narrow). */
914
914
  .fdy-filelist--grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));}
@@ -927,7 +927,7 @@ a { color: var(--color-primary); }
927
927
  .fdy-file--error .fdy-file__icon{color:var(--color-danger);}
928
928
  .fdy-file--error .fdy-file__sub{color:var(--color-danger-strong);}
929
929
 
930
- /* Freeday Filter bar (a consistent row of filters: search + selects + actions).
930
+ /* Freeday. Filter bar (a consistent row of filters: search + selects + actions).
931
931
  * Solves the "ragged filter row": apps improvised flex rows with ad-hoc
932
932
  * flex/min-width, so grow fields ballooned while selects stayed tiny and no two
933
933
  * screens matched. Here every field snaps to a shared width rhythm; one field may
@@ -936,7 +936,7 @@ a { color: var(--color-primary); }
936
936
  .fdy-filterbar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-3) var(--space-4);}
937
937
  /* Composed with a table toolbar (one element, both classes): the fields decide the alignment.
938
938
  * `.fdy-table-toolbar` sets align-items:center (right for bare buttons) at equal specificity, and
939
- * being later in the bundle it would otherwise win floating labelled fields' neighbours out of line.
939
+ * being later in the bundle it would otherwise win, floating labelled fields' neighbours out of line.
940
940
  * This 2-class selector (0,2,0) settles it toward the filterbar's flex-end regardless of source order. */
941
941
  .fdy-table-toolbar.fdy-filterbar{align-items:flex-end;}
942
942
  /* Fields are direct children. Drop the standalone field's 22rem cap; default to the
@@ -944,7 +944,7 @@ a { color: var(--color-primary); }
944
944
  .fdy-filterbar>.fdy-field{max-width:none;flex:0 0 auto;width:12rem;}
945
945
  /* Controls fill their field's column (drop the standalone input-group/combo caps). */
946
946
  .fdy-filterbar>.fdy-field .fdy-input-group,.fdy-filterbar>.fdy-field .fdy-combo{max-width:none;width:100%;}
947
- /* Column-width rhythm pick one per field so bars across the app line up. */
947
+ /* Column-width rhythm, pick one per field so bars across the app line up. */
948
948
  .fdy-filterbar>.fdy-field--w-sm{width:9rem;}
949
949
  .fdy-filterbar>.fdy-field--w-lg{width:15rem;}
950
950
  .fdy-filterbar>.fdy-field--w-xl{width:18rem;}
@@ -954,11 +954,11 @@ a { color: var(--color-primary); }
954
954
  .fdy-filterbar>.fdy-field--w-grow{flex:1 1 16rem;width:auto;min-width:12rem;}
955
955
  /* Trailing actions (buttons) pushed to the end, aligned with the controls' baseline.
956
956
  * Wrap note: a wrapped bar breaks flex lines on each item's *flex-basis* (the grow field's
957
- * `flex:1 1 16rem` reserves 16rem for line-breaking), NOT its rendered width so the grow basis,
957
+ * `flex:1 1 16rem` reserves 16rem for line-breaking), NOT its rendered width, so the grow basis,
958
958
  * not the visible width, decides the wrap point. Margins can't keep the actions off a lonely wrapped
959
959
  * row; to avoid stranded actions, tune the --w-grow basis or drop a field so the bar fits one row. */
960
- /* A control that carries NO label of its own a checkbox, a switch, a lone
961
- button in a bar whose `align-items:flex-end` exists to line up LABELLED
960
+ /* A control that carries NO label of its own, a checkbox, a switch, a lone
961
+ button, in a bar whose `align-items:flex-end` exists to line up LABELLED
962
962
  fields. Flex-end puts such a control's bottom on the inputs' bottom, so a
963
963
  20px checkbox sits well below the 32px input's centre line and reads as
964
964
  dropped. Giving it the height of a control lands its box exactly where an
@@ -966,7 +966,7 @@ a { color: var(--color-primary); }
966
966
  line. Raised from IDU_EMATE_APPL_WEB (#014). */
967
967
  .fdy-filterbar>.fdy-check,.fdy-filterbar>.fdy-radio,.fdy-filterbar>.fdy-switch,.fdy-filterbar>.fdy-btn{min-height:var(--control-h);}
968
968
  .fdy-filterbar__actions{display:flex;align-items:center;gap:var(--space-2);margin-left:auto;}
969
- /* Opt-in: sit the actions inline right after the last field instead of at the far edge so when the
969
+ /* Opt-in: sit the actions inline right after the last field instead of at the far edge, so when the
970
970
  * bar wraps they read as attached to the filters rather than shoved to the right of a lonely row. */
971
971
  .fdy-filterbar--actions-inline .fdy-filterbar__actions{margin-left:0;}
972
972
  /* Stack on narrow screens so nothing overflows the row. */
@@ -975,16 +975,16 @@ a { color: var(--color-primary); }
975
975
  .fdy-filterbar__actions{width:100%;margin-left:0;}
976
976
  }
977
977
 
978
- /* Freeday Form grid (document/header fields laid out in aligned columns).
978
+ /* Freeday. Form grid (document/header fields laid out in aligned columns).
979
979
  * The two-dimensional counterpart to the single-column .fdy-field stack and the
980
980
  * one-row .fdy-filterbar: related fields sit in columns that reflow to one on narrow
981
981
  * screens. Fields are the standard .fdy-field; a field with --full spans the whole row
982
- * (e.g. an address or note). Zero JS pure layout over .fdy-field. */
982
+ * (e.g. an address or note). Zero JS, pure layout over .fdy-field. */
983
983
  .fdy-form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(14rem,1fr));gap:var(--space-4) var(--space-5);}
984
984
  .fdy-form-grid>.fdy-field{max-width:none;}
985
985
  .fdy-form-grid>.fdy-field--full{grid-column:1/-1;}
986
986
 
987
- /* Freeday Input group (prefix/suffix addons, e.g. Rp / % / icon) */
987
+ /* Freeday. Input group (prefix/suffix addons, e.g. Rp / % / icon) */
988
988
  .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);}
989
989
  .fdy-input-group:hover{border-color:var(--color-text-muted);}
990
990
  .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);}
@@ -997,34 +997,34 @@ a { color: var(--color-primary); }
997
997
  .fdy-input-group .fdy-input:focus{box-shadow:none;}
998
998
  .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);}
999
999
  .fdy-input-group__addon:last-child{border-right:0;border-left:var(--bw) solid var(--color-border);}
1000
- /* Borderless leading icon (e.g. search magnifier) self-contained like __btn so it
1000
+ /* Borderless leading icon (e.g. search magnifier), self-contained like __btn so it
1001
1001
  centers standalone (no base .fdy-input-group__addon required). */
1002
1002
  .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);}
1003
1003
  .fdy-input-group__addon--icon svg{display:block;width:1rem;height:1rem;}
1004
- /* The leading icon already provides the left inset don't let the input add its full left padding on top. */
1004
+ /* The leading icon already provides the left inset, don't let the input add its full left padding on top. */
1005
1005
  .fdy-input-group__addon--icon + .fdy-input{padding-left:var(--space-2);}
1006
- /* Trailing action button (e.g. choose-from-list magnifier) a real, focusable control. */
1006
+ /* Trailing action button (e.g. choose-from-list magnifier), a real, focusable control. */
1007
1007
  .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);}
1008
1008
  .fdy-input-group__btn:hover{background:var(--color-surface-3);color:var(--color-text);}
1009
1009
  .fdy-input-group__btn:focus-visible{outline:none;color:var(--color-text);box-shadow:inset 0 0 0 2px var(--color-primary);}
1010
1010
  .fdy-input-group__btn svg{display:block;width:1.1rem;height:1.1rem;}
1011
1011
  /* Leading action button (the "−" of a number stepper): the divider belongs on the other side. */
1012
1012
  .fdy-input-group__btn:first-child{border-left:0;border-right:var(--bw) solid var(--color-border);}
1013
- /* At a bound, or on a disabled/read-only field, the button must look as inert as it acts same dim
1013
+ /* At a bound, or on a disabled/read-only field, the button must look as inert as it acts, same dim
1014
1014
  + not-allowed as .fdy-input:disabled. :hover still matches a disabled button, so it is withdrawn. */
1015
1015
  .fdy-input-group__btn:disabled{opacity:.5;cursor:not-allowed;}
1016
1016
  .fdy-input-group__btn:disabled:hover{background:var(--color-surface-2);color:var(--color-text-muted);}
1017
- /* Error state must win over the neutralizing rules above promote to the wrapper. */
1017
+ /* Error state must win over the neutralizing rules above, promote to the wrapper. */
1018
1018
  .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);}
1019
1019
  .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);}
1020
1020
 
1021
- /* Freeday Field & Input */
1021
+ /* Freeday. Field & Input */
1022
1022
  .fdy-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
1023
- /* A grouped field is legitimately a <fieldset> (it needs a <legend>) e.g. a radio/checkbox group.
1023
+ /* A grouped field is legitimately a <fieldset> (it needs a <legend>), e.g. a radio/checkbox group.
1024
1024
  Neutralise the UA groove border / padding / min-inline-size so it lays out identically to the
1025
1025
  <div> form (min-inline-size:min-content would otherwise block the filterbar column widths). */
1026
1026
  fieldset.fdy-field{border:0;margin:0;padding:0;min-inline-size:0;}
1027
- /* The rendered legend is laid out outside the fieldset's flex flow, so `gap` never reaches it
1027
+ /* The rendered legend is laid out outside the fieldset's flex flow, so `gap` never reaches it,
1028
1028
  measured 0px between legend and first control. This is the spacing contract a grouped field
1029
1029
  needs; it matches .fdy-field's own gap so the group reads as one rhythm. */
1030
1030
  fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
@@ -1042,40 +1042,40 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1042
1042
  .fdy-input[readonly]:hover,.fdy-textarea[readonly]:hover{border-color:var(--color-border);}
1043
1043
  .fdy-input[readonly]:focus-visible,.fdy-textarea[readonly]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
1044
1044
  /* Numbers (#44): the user agent draws its own spin buttons inside the field, in OS widget colours no
1045
- theme reaches on a dark surface they read as a light-grey artefact glued to a themed field, and
1045
+ theme reaches, on a dark surface they read as a light-grey artefact glued to a themed field, and
1046
1046
  it is the only unthemed control on the page. Removed so .fdy-input looks the same whatever its
1047
1047
  type. `appearance:textfield` is the Firefox half, the pseudo-element the Blink/WebKit half; both
1048
- are needed. This trades away the mouse-only increment arrow keys on a focused number input still
1048
+ are needed. This trades away the mouse-only increment, arrow keys on a focused number input still
1049
1049
  work, and [data-fdy-number] gives the affordance back on the kit's terms. */
1050
1050
  .fdy-input[type="number"]{appearance:textfield;-webkit-appearance:textfield;}
1051
1051
  .fdy-input[type="number"]::-webkit-outer-spin-button,.fdy-input[type="number"]::-webkit-inner-spin-button{appearance:none;-webkit-appearance:none;margin:0;}
1052
1052
  .fdy-input--error,.fdy-input[aria-invalid="true"],.fdy-textarea[aria-invalid="true"]{border-color:var(--color-danger);}
1053
1053
  .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);}
1054
1054
  /* Required marker. The control already carries `required`, so the asterisk is decoration: the
1055
- `/ ""` is CSS alt text the glyph is painted but exposes NOTHING to the accessibility tree, so a
1055
+ `/ ""` is CSS alt text, the glyph is painted but exposes NOTHING to the accessibility tree, so a
1056
1056
  screen reader does not read "star" after every label. That is stricter than a <span> an app has to
1057
1057
  remember to mark aria-hidden, and it cannot be forgotten. */
1058
1058
  .fdy-label--required::after{content:"*" / "";margin-left:var(--space-1);color:var(--color-danger);}
1059
1059
  .fdy-help{font-size:var(--text-xs);color:var(--color-text-muted);}
1060
1060
  .fdy-help--error{color:var(--color-danger);}
1061
1061
 
1062
- /* Freeday Keyboard key */
1062
+ /* Freeday. Keyboard key */
1063
1063
  .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);}
1064
1064
 
1065
- /* Freeday List: the FLAT row container.
1065
+ /* Freeday. List: the FLAT row container.
1066
1066
  *
1067
1067
  * USAGE.md §3 puts list rows among the surfaces that should be flat, but until now the only
1068
- * container the kit shipped was .fdy-card which carries --shadow-lift (a real 34px lift). A
1068
+ * container the kit shipped was .fdy-card, which carries --shadow-lift (a real 34px lift). A
1069
1069
  * responsive table that becomes a list below `md` therefore had to choose between a stack of
1070
1070
  * shadowed cards (against the doctrine) or a hand-built box, which needs a colour and so escapes
1071
1071
  * the token system. This is that missing container: one flat surface, hairline dividers, no shadow.
1072
1072
  *
1073
1073
  * Not to be confused with .fdy-list-reset (base.css), which only strips UA bullets/indent.
1074
1074
  * Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
1075
- /* position:relative containing block for out-of-flow row content (see base.css). */
1075
+ /* position:relative, containing block for out-of-flow row content (see base.css). */
1076
1076
  .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;}
1077
1077
  /* A row that hosts a stretched target has to be the anchor itself (#008 §1). Without this the
1078
- overlay resolves against .fdy-list which IS positioned so EVERY row's target covers the whole
1078
+ overlay resolves against .fdy-list, which IS positioned, so EVERY row's target covers the whole
1079
1079
  list and the last one in the DOM wins: measured, a click on row one opens row two, under row one's
1080
1080
  name, which looks exactly like working software. Opt-in via :has(), so a row that doesn't use the
1081
1081
  pattern is untouched and nothing an app absolutely positions inside a plain row starts resolving
@@ -1084,7 +1084,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1084
1084
  .fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
1085
1085
  /* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
1086
1086
  * of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
1087
- * <li>s a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
1087
+ * <li>s, a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
1088
1088
  .fdy-list__row + .fdy-list__row,
1089
1089
  .fdy-list > * + * > .fdy-list__row{border-top:var(--bw) solid var(--color-border-muted);}
1090
1090
  /* A row that is itself the control: render it as <button>/<a> and the UA box is reset without
@@ -1093,7 +1093,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1093
1093
  .fdy-list__row--button:hover,.fdy-list__row--interactive:hover{background:var(--color-surface-2);}
1094
1094
  .fdy-list__row--button:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary);}
1095
1095
  .fdy-list__row--interactive{cursor:pointer;}
1096
- /* Disabled row. The reset above adopts the UA button box, but not its disabled state so a row
1096
+ /* Disabled row. The reset above adopts the UA button box, but not its disabled state, so a row
1097
1097
  disabled mid-flight (a redirect in progress, say) kept lighting up and kept a pointer cursor:
1098
1098
  a control answering the pointer while refusing input. Same contract as every other disabled
1099
1099
  control in the kit (dim + not-allowed), hover withdrawn, both the native and the aria- form. */
@@ -1106,7 +1106,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1106
1106
  .fdy-list__aside{flex:none;display:flex;align-items:center;gap:var(--space-2);}
1107
1107
  /* Density: rows tighten with the rest of the kit (--space-* mid-range steps under compact). */
1108
1108
 
1109
- /* Freeday Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
1109
+ /* Freeday. Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
1110
1110
  .fdy-menu-wrap{position:relative;display:inline-flex;}
1111
1111
  .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);}
1112
1112
  .fdy-menu[hidden]{display:none;}
@@ -1114,7 +1114,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1114
1114
  .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;}
1115
1115
  .fdy-menu__item:hover,.fdy-menu__item.is-highlighted{background:var(--color-surface-3);}
1116
1116
  /* Focus is not hover. The enhancer moves real DOM focus (freeday-menu.js `.focus()`), so this is
1117
- the only thing a keyboard user has to go on with the hover fill alone, arrowing through the
1117
+ the only thing a keyboard user has to go on, with the hover fill alone, arrowing through the
1118
1118
  menu is invisible. Same ring as .fdy-nav__item, which got this right. Reported twice. */
1119
1119
  .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);}
1120
1120
  .fdy-menu__item:disabled{opacity:.5;cursor:not-allowed;background:transparent;}
@@ -1123,7 +1123,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1123
1123
  .fdy-menu__sep{height:0;margin:var(--space-1) 0;border:0;border-top:var(--bw) solid var(--color-border-muted);}
1124
1124
  @media (prefers-reduced-motion:no-preference){.fdy-menu:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
1125
1125
 
1126
- /* Split button main action + attached menu toggle (caret) */
1126
+ /* Split button, main action + attached menu toggle (caret) */
1127
1127
  .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);}
1128
1128
  /* One control: the two halves must stay coplanar. Neutralize each segment's own
1129
1129
  lift + drop-shadow (they would misalign the seam / glow a single half); the
@@ -1138,7 +1138,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1138
1138
  .fdy-btn-split__toggle:hover,.fdy-btn-split__toggle:focus-visible{z-index:1;}
1139
1139
  .fdy-btn-split__toggle svg{display:block;width:1rem;height:1rem;}
1140
1140
 
1141
- /* Freeday Modal (native <dialog>) */
1141
+ /* Freeday. Modal (native <dialog>) */
1142
1142
  .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;}
1143
1143
  .fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
1144
1144
  .fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
@@ -1161,7 +1161,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1161
1161
  .fdy-modal[open]::backdrop{opacity:0;}
1162
1162
  }
1163
1163
 
1164
- /* Freeday Pagination */
1164
+ /* Freeday. Pagination */
1165
1165
  .fdy-pagination__list{display:flex;align-items:center;gap:var(--space-1);list-style:none;margin:0;padding:0;}
1166
1166
  .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);}
1167
1167
  .fdy-pagination__link:hover{background:var(--color-surface-2);border-color:var(--color-border);}
@@ -1170,7 +1170,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1170
1170
  .fdy-pagination__link[aria-disabled="true"],.fdy-pagination__link:disabled{opacity:.5;cursor:not-allowed;pointer-events:none;}
1171
1171
  .fdy-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;color:var(--color-text-subtle);}
1172
1172
 
1173
- /* Freeday Progress */
1173
+ /* Freeday. Progress */
1174
1174
  .fdy-progress{height:.5rem;border-radius:var(--radius-full);background:var(--color-surface-3);overflow:hidden;}
1175
1175
  .fdy-progress__bar{height:100%;border-radius:inherit;background:var(--color-primary);transition:width var(--dur-base) var(--ease-standard);}
1176
1176
  .fdy-progress--indeterminate{position:relative;}
@@ -1178,7 +1178,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1178
1178
  @keyframes fdy-progress-indet{0%{margin-left:-40%;}100%{margin-left:100%;}}
1179
1179
  @media (prefers-reduced-motion:reduce){.fdy-progress--indeterminate .fdy-progress__bar{animation:none;width:100%;opacity:.5;}}
1180
1180
 
1181
- /* Freeday Rating (star rating). Interactive = native radio group (arrow keys, form-assoc);
1181
+ /* Freeday. Rating (star rating). Interactive = native radio group (arrow keys, form-assoc);
1182
1182
  * filled vs empty differ by fill-vs-outline (not colour alone). Enhanced by freeday-rating.js. */
1183
1183
  .fdy-rating{display:inline-flex;gap:var(--space-1);}
1184
1184
  .fdy-rating__star{display:inline-flex;line-height:0;cursor:pointer;}
@@ -1191,17 +1191,17 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1191
1191
  .fdy-rating--disabled .fdy-rating__star{cursor:not-allowed;}
1192
1192
  .fdy-rating--disabled .fdy-rating__star svg{opacity:.5;}
1193
1193
  /* Invalid: tint the (outline) empty stars danger so a required-but-empty rating reads as an error,
1194
- paired with a help/error message never colour alone. */
1194
+ paired with a help/error message, never colour alone. */
1195
1195
  .fdy-rating--error .fdy-rating__star svg{stroke:var(--color-danger);}
1196
1196
  .fdy-rating--sm .fdy-rating__star svg{width:1.1rem;height:1.1rem;}
1197
1197
 
1198
- /* Freeday Selection controls */
1198
+ /* Freeday. Selection controls */
1199
1199
  /* `flex:none` on every box, track and standalone checkbox is load-bearing, not tidiness.
1200
1200
  `.fdy-check` is an inline-flex row of [box, label], and a flex item with a WIDTH is still
1201
- shrinkable so as soon as the label wrapped to a second line the box gave up horizontal space
1201
+ shrinkable, so as soon as the label wrapped to a second line the box gave up horizontal space
1202
1202
  to it and rendered an 18px-tall rectangle 13px wide. Squashed, off-square, and different from
1203
1203
  every sibling whose label happened to be short: a form of five checkboxes showed three sizes.
1204
- Nothing in the declarations looked wrong, because the width was never overridden it was
1204
+ Nothing in the declarations looked wrong, because the width was never overridden, it was
1205
1205
  negotiated away. */
1206
1206
  .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;}
1207
1207
  .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);}
@@ -1219,7 +1219,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1219
1219
  .fdy-switch input:checked{background:var(--color-primary);}
1220
1220
  .fdy-switch input:checked::before{transform:translateX(calc(var(--control-switch-w) - var(--control-switch-h)));}
1221
1221
 
1222
- /* Standalone checkbox (e.g. table row/select-all) supports :indeterminate */
1222
+ /* Standalone checkbox (e.g. table row/select-all), supports :indeterminate */
1223
1223
  .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);}
1224
1224
  .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%);}
1225
1225
  .fdy-checkbox:checked,.fdy-checkbox:indeterminate{background:var(--color-primary);border-color:var(--color-primary);}
@@ -1228,7 +1228,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1228
1228
  .fdy-checkbox:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
1229
1229
  .fdy-checkbox:disabled{opacity:.5;cursor:not-allowed;}
1230
1230
 
1231
- /* Freeday Skeleton */
1231
+ /* Freeday. Skeleton */
1232
1232
  .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;}
1233
1233
  .fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
1234
1234
  .fdy-skeleton--title{height:1.2rem;width:40%;}
@@ -1241,7 +1241,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1241
1241
  @keyframes fdy-skeleton{to{background-position:-200% 0;}}
1242
1242
  @media (prefers-reduced-motion:reduce){.fdy-skeleton{animation:none;}}
1243
1243
 
1244
- /* Freeday Slider (native <input type="range"> themed via accent-color: keeps native
1244
+ /* Freeday. Slider (native <input type="range"> themed via accent-color: keeps native
1245
1245
  * keyboard + ARIA, filled track and thumb come free). Optional value output + scale.
1246
1246
  * freeday-slider.js just mirrors the value into an <output for="…">. */
1247
1247
  .fdy-slider{width:100%;max-width:22rem;height:1.5rem;accent-color:var(--color-primary);cursor:pointer;}
@@ -1254,14 +1254,14 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1254
1254
  .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;}
1255
1255
  .fdy-slider__scale{display:flex;justify-content:space-between;font-size:var(--text-xs);color:var(--color-text-muted);font-variant-numeric:tabular-nums;}
1256
1256
 
1257
- /* Freeday Spinner */
1257
+ /* Freeday. Spinner */
1258
1258
  .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;}
1259
1259
  .fdy-spinner--sm{width:1rem;height:1rem;border-width:2px;}
1260
1260
  .fdy-spinner--lg{width:2.5rem;height:2.5rem;border-width:3px;}
1261
1261
  @keyframes fdy-spin{to{transform:rotate(360deg);}}
1262
1262
  @media (prefers-reduced-motion:reduce){.fdy-spinner{animation-duration:2s;}}
1263
1263
 
1264
- /* Freeday States (empty / loading / error placeholders) */
1264
+ /* Freeday. States (empty / loading / error placeholders) */
1265
1265
  .fdy-state{display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--space-2);padding:var(--space-10) var(--space-6);}
1266
1266
  .fdy-state__icon{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;color:var(--color-text-subtle);}
1267
1267
  .fdy-state__icon svg{width:100%;height:100%;}
@@ -1270,7 +1270,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1270
1270
  .fdy-state__actions{margin-top:var(--space-2);display:flex;gap:var(--space-2);flex-wrap:wrap;justify-content:center;}
1271
1271
  .fdy-state--danger .fdy-state__icon{color:var(--color-danger-strong);}
1272
1272
 
1273
- /* Freeday Stepper / Wizard (progress header + optional panels).
1273
+ /* Freeday. Stepper / Wizard (progress header + optional panels).
1274
1274
  * Enhanced by freeday-stepper.js for prev/next navigation. */
1275
1275
  .fdy-stepper{display:flex;list-style:none;margin:0;padding:0;}
1276
1276
  .fdy-step{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;text-align:center;min-width:0;}
@@ -1280,7 +1280,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1280
1280
  .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%;}
1281
1281
  .fdy-step__btn:disabled{cursor:default;}
1282
1282
  /* The marker carries the lift itself, NOT `__btn`. The connector above is position:absolute with
1283
- z-index:0, and a positioned box paints after in-flow inline content in the same stacking context
1283
+ z-index:0, and a positioned box paints after in-flow inline content in the same stacking context,
1284
1284
  so a marker that is merely inline (an indicator-only stepper with no `__btn`) gets painted THROUGH
1285
1285
  by the connector line. Lifting the part that must always exist makes that assembly impossible;
1286
1286
  `__btn`'s own z-index stays, harmlessly, for the navigable case. */
@@ -1297,8 +1297,8 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1297
1297
  .fdy-step-panels{margin-top:var(--space-5);}
1298
1298
  .fdy-step-nav{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-5);}
1299
1299
 
1300
- /* Freeday Table */
1301
- /* position:relative on every scroller/clipper it makes the box the containing block for its own
1300
+ /* Freeday. Table */
1301
+ /* position:relative on every scroller/clipper, it makes the box the containing block for its own
1302
1302
  absolutely positioned content (.fdy-visually-hidden above all), which `overflow` alone does NOT
1303
1303
  clip. Without it a hidden label in a wide table scrolls the whole page. See base.css. */
1304
1304
  .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);}
@@ -1309,7 +1309,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1309
1309
  .fdy-table tbody tr:last-child td{border-bottom:0;}
1310
1310
  .fdy-table tbody tr{transition:background-color var(--dur-fast) var(--ease-standard);}
1311
1311
  .fdy-table tbody tr:hover{background:var(--color-surface-2);}
1312
- /* Monospace data ids, codes, IPs, timestamps. Alignment-neutral so it works in any cell
1312
+ /* Monospace data, ids, codes, IPs, timestamps. Alignment-neutral so it works in any cell
1313
1313
  or inline (`<span class="fdy-mono">`), not just numeric columns. */
1314
1314
  .fdy-mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums;}
1315
1315
  /* Numeric cells are mono + right-aligned. Kept self-contained (not composed from .fdy-mono)
@@ -1336,7 +1336,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1336
1336
  .fdy-table__selcol{width:1px;white-space:nowrap;}
1337
1337
  .fdy-table tbody tr[aria-selected="true"],.fdy-table tbody tr[aria-selected="true"]:hover{background:var(--color-primary-soft);}
1338
1338
 
1339
- /* Frozen header row and/or first column a rate matrix, a timetable, any grid read
1339
+ /* Frozen header row and/or first column, a rate matrix, a timetable, any grid read
1340
1340
  against two axes at once.
1341
1341
 
1342
1342
  This modifier exists because `position:sticky` alone is NOT enough. With
@@ -1350,12 +1350,12 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1350
1350
  /* Freezing the HEADER ROW is its own modifier because it needs a vertical
1351
1351
  scrollport (`.fdy-table-scroll--frozen`). A table that only freezes columns
1352
1352
  scrolls with the PAGE, and `top:0` against the page sticks the header under
1353
- the viewport edge over the app's own top bar which is worse than not
1353
+ the viewport edge, over the app's own top bar, which is worse than not
1354
1354
  freezing it. Ask for the axis you actually have. */
1355
1355
  .fdy-table--sticky-head thead th{position:sticky;top:0;z-index:2;border-bottom:var(--bw) solid var(--color-border);}
1356
1356
 
1357
1357
  /* Freezing a COLUMN: put `.fdy-table__freeze` on every cell of it, header
1358
- included, and give each frozen column its own `--fdy-freeze-left` 0 for the
1358
+ included, and give each frozen column its own `--fdy-freeze-left`. 0 for the
1359
1359
  first, the summed widths of the frozen columns left of it after that. One
1360
1360
  frozen column needs no variable at all. Mark the last one `--edge` so the
1361
1361
  reader can see where the frozen block ends.
@@ -1364,12 +1364,12 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1364
1364
  widths, which CSS cannot sum, and hard-coding them here would only work for
1365
1365
  tables whose columns are all the same width. */
1366
1366
  .fdy-table--sticky .fdy-table__freeze{position:sticky;left:var(--fdy-freeze-left,0);}
1367
- /* A frozen body cell slides OVER the data, so it must be OPAQUE otherwise the
1367
+ /* A frozen body cell slides OVER the data, so it must be OPAQUE, otherwise the
1368
1368
  cells beneath show through it. A `th` already carries the header tint from the
1369
1369
  base rule, and that tint is usually the whole point: it is what marks a column
1370
1370
  as identity rather than data. So only a `td` is given a surface here. A table
1371
- that wants its frozen `th` to read as a body row instead a rate matrix, where
1372
- a hovered row should band unbroken across the freeze line says so itself. */
1371
+ that wants its frozen `th` to read as a body row instead, a rate matrix, where
1372
+ a hovered row should band unbroken across the freeze line, says so itself. */
1373
1373
  .fdy-table--sticky tbody .fdy-table__freeze{z-index:1;}
1374
1374
  .fdy-table--sticky tbody td.fdy-table__freeze{background:var(--color-surface);}
1375
1375
  .fdy-table--sticky tbody tr:hover td.fdy-table__freeze{background:var(--color-surface-2);}
@@ -1392,19 +1392,19 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1392
1392
  .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);}
1393
1393
  .fdy-table-footer__info{font-size:var(--text-sm);color:var(--color-text-muted);}
1394
1394
  /* Rows per page. `margin-left:auto` eats the free space, so the size control and the pager travel
1395
- together on the right while the range stays left no wrapper element, which is what keeps the
1395
+ together on the right while the range stays left, no wrapper element, which is what keeps the
1396
1396
  footer's DOM byte-identical for the tables that don't offer one (#008). The control is the kit's
1397
1397
  own `.fdy-combo`, never a native <select>: an OS menu is unthemeable, and on macOS it drops a
1398
- dark panel into a light page which is the reason FdyCombo exists at all. Sized to its content:
1398
+ dark panel into a light page, which is the reason FdyCombo exists at all. Sized to its content:
1399
1399
  `.fdy-combo` is display:block at max-width 22rem, which in a footer is a bar-wide box for two
1400
- digits. Its HEIGHT is left alone on purpose `--control-h`, the same as every other control on
1400
+ digits. Its HEIGHT is left alone on purpose, `--control-h`, the same as every other control on
1401
1401
  the page. It briefly carried 2.25rem to match the pager links beside it, which made the one
1402
1402
  combobox in a compact app 4px taller than all the others; a pager link is a nav button, and the
1403
1403
  thing this must agree with is the form controls. */
1404
1404
  .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);}
1405
1405
  .fdy-table-footer__size .fdy-combo{width:5.5rem;}
1406
1406
 
1407
- /* Column filter funnel button injected (enhancer) or rendered (FdyTable) into a filterable <th> */
1407
+ /* Column filter, funnel button injected (enhancer) or rendered (FdyTable) into a filterable <th> */
1408
1408
  .fdy-table__filterwrap{display:inline-flex;align-items:center;vertical-align:middle;}
1409
1409
  .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;}
1410
1410
  .fdy-table__filterbtn:hover{background:var(--color-surface-3);color:var(--color-text);}
@@ -1421,7 +1421,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1421
1421
  .fdy-filter__range .fdy-input{width:6rem;}
1422
1422
  .fdy-filter__foot{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-3);}
1423
1423
 
1424
- /* Bulk-action bar appears above the table body once rows are selected */
1424
+ /* Bulk-action bar, appears above the table body once rows are selected */
1425
1425
  .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);}
1426
1426
  .fdy-table-bulkbar[hidden]{display:none;}
1427
1427
  .fdy-table-bulkbar__count{font-weight:var(--weight-semibold);white-space:nowrap;}
@@ -1429,14 +1429,14 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1429
1429
  .fdy-table-bulkbar__actions{display:flex;gap:var(--space-2);}
1430
1430
 
1431
1431
  /* Controls inside a data row. `.fdy-input` and friends are width:100%, which in an auto-layout
1432
- table means they contribute NO intrinsic width the column shrinks to whatever the header text
1432
+ table means they contribute NO intrinsic width, the column shrinks to whatever the header text
1433
1433
  needs and the control collapses with it. A NOTE column ends up a box too narrow to read what you
1434
1434
  typed into it. The floor is per-control rather than on the column, because the table does not
1435
1435
  know which of its columns hold controls. */
1436
1436
  .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;}
1437
1437
 
1438
- /* Freeday Tabs (WAI-ARIA APG) */
1439
- /* position:relative containing block for out-of-flow content in the tabs (see base.css). */
1438
+ /* Freeday. Tabs (WAI-ARIA APG) */
1439
+ /* position:relative, containing block for out-of-flow content in the tabs (see base.css). */
1440
1440
  .fdy-tabs__list{position:relative;display:flex;gap:var(--space-1);border-bottom:var(--bw) solid var(--color-border);overflow-x:auto;}
1441
1441
  .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);}
1442
1442
  .fdy-tabs__tab:hover{color:var(--color-text);}
@@ -1444,7 +1444,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1444
1444
  .fdy-tabs__tab:disabled:hover,.fdy-tabs__tab[aria-disabled="true"]:hover{color:var(--color-text-muted);}
1445
1445
  /* `aria-current="page"` is honoured alongside `aria-selected="true"` so the tab look can be used for
1446
1446
  ROUTE-driven sub-navigation (/settings/profile · /settings/billing), where the tabs are real links.
1447
- A link cannot be `aria-selected` that is invalid ARIA on an anchor; `aria-current` is the only
1447
+ A link cannot be `aria-selected`, that is invalid ARIA on an anchor; `aria-current` is the only
1448
1448
  correct marker, and it is what a router sets. Use `.fdy-tabs` + `freeday-tabs.js` (roving tabindex,
1449
1449
  a panel per tab) for in-page tabs; use these classes on plain links for routed ones, and
1450
1450
  `.fdy-nav--horizontal` for an application's PRIMARY navigation. */
@@ -1455,7 +1455,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1455
1455
  /* Reveal the newly-shown panel on tab switch (fade + rise), reduced-motion gated. */
1456
1456
  @media (prefers-reduced-motion:no-preference){.fdy-tabs__panel:not([hidden]){animation:fdy-combo-in var(--dur-base) var(--ease-standard);}}
1457
1457
 
1458
- /* Freeday Timeline (vertical event feed: dot markers on a connector line). Pure CSS. */
1458
+ /* Freeday. Timeline (vertical event feed: dot markers on a connector line). Pure CSS. */
1459
1459
  .fdy-timeline{list-style:none;margin:0;padding:0;}
1460
1460
  .fdy-timeline__item{position:relative;padding:0 0 var(--space-5) var(--space-6);border-left:2px solid var(--color-border);}
1461
1461
  .fdy-timeline__item:last-child{border-left-color:transparent;padding-bottom:0;}
@@ -1468,7 +1468,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1468
1468
  .fdy-timeline__title{margin:0;font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--color-text);}
1469
1469
  .fdy-timeline__text{margin:var(--space-1) 0 0;font-size:var(--text-sm);color:var(--color-text-muted);line-height:var(--leading-normal);}
1470
1470
 
1471
- /* Freeday Time picker (input-styled trigger + scrollable time-list popover).
1471
+ /* Freeday. Time picker (input-styled trigger + scrollable time-list popover).
1472
1472
  * Built by freeday-timepicker.js from an empty [data-fdy-timepicker] wrapper.
1473
1473
  * Trigger visual mirrors the date picker for a consistent control family. */
1474
1474
  .fdy-timepicker{position:relative;display:inline-block;width:100%;max-width:11rem;}
@@ -1492,14 +1492,14 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1492
1492
  .fdy-timepicker__opt[aria-selected="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
1493
1493
  @media (prefers-reduced-motion:no-preference){.fdy-timepicker__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
1494
1494
 
1495
- /* Freeday Toast (transient notification) */
1495
+ /* Freeday. Toast (transient notification) */
1496
1496
  /* The region is a POPOVER, not merely a high z-index.
1497
- `.fdy-modal` is a native <dialog> opened with showModal(), which puts it in the TOP LAYER a
1497
+ `.fdy-modal` is a native <dialog> opened with showModal(), which puts it in the TOP LAYER, a
1498
1498
  layer above the whole z-index universe, so no number here could ever beat it. A toast raised by
1499
1499
  an action taken inside a modal was painted behind that modal's backdrop: dimmed, unreadable, and
1500
1500
  most of it under the dialog itself. `freeday-toast.js` calls showPopover() as each toast is
1501
1501
  added, which puts the region in the top layer too, and last-in wins there.
1502
- Everything after `pointer-events` is undoing the UA's [popover] defaults it arrives with
1502
+ Everything after `pointer-events` is undoing the UA's [popover] defaults, it arrives with
1503
1503
  inset:0, auto margins, a border, padding, a Canvas background and overflow:auto, none of which a
1504
1504
  toast stack wants. Where showPopover() is unavailable the element is still an ordinary fixed
1505
1505
  block at z-index 200, which is exactly what it was before. */
@@ -1522,14 +1522,14 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1522
1522
  @media (prefers-reduced-motion:no-preference){.fdy-toast{animation:fdy-toast-in var(--dur-base) var(--ease-spring);}}
1523
1523
  @keyframes fdy-toast-in{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:none;}}
1524
1524
 
1525
- /* Freeday Tooltip (CSS-only, hover/focus) */
1525
+ /* Freeday. Tooltip (CSS-only, hover/focus) */
1526
1526
  .fdy-tooltip-wrap{position:relative;display:inline-flex;}
1527
1527
  .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);}
1528
1528
  .fdy-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);border:.3rem solid transparent;border-top-color:var(--color-inverse-surface);}
1529
1529
  .fdy-tooltip-wrap:hover .fdy-tooltip,.fdy-tooltip-wrap:focus-within .fdy-tooltip{opacity:1;visibility:visible;transform:translateX(-50%) translateY(0);}
1530
1530
  @media (prefers-reduced-motion:reduce){.fdy-tooltip{transition:none;transform:translateX(-50%);}}
1531
1531
 
1532
- /* Freeday Tree view (hierarchy via nested native <details>: keyboard-toggle, zero-JS).
1532
+ /* Freeday. Tree view (hierarchy via nested native <details>: keyboard-toggle, zero-JS).
1533
1533
  * Branches expand/collapse; leaves are plain rows. Chevron + optional folder/file icons. */
1534
1534
  .fdy-tree{list-style:none;margin:0;padding:0;font-size:var(--text-sm);}
1535
1535
  .fdy-tree ul{list-style:none;margin:0;padding-left:var(--space-4);}
@@ -1546,7 +1546,7 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
1546
1546
  /* Disabled row (branch summary or leaf): dimmed, no hover, not interactive. */
1547
1547
  .fdy-tree__leaf[aria-disabled="true"],.fdy-tree__branch[aria-disabled="true"] > summary{opacity:.5;cursor:not-allowed;pointer-events:none;}
1548
1548
 
1549
- /* Checkbox variant a selectable tree (cascading parent/child via freeday-tree.js).
1549
+ /* Checkbox variant, a selectable tree (cascading parent/child via freeday-tree.js).
1550
1550
  * A native <input class="fdy-checkbox"> sits at the start of every row; a partially
1551
1551
  * selected branch shows the native :indeterminate dash. The checkbox stops click
1552
1552
  * propagation, so toggling selection never expands/collapses the branch. */