@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
@@ -6,7 +6,7 @@
6
6
  <title>Freeday — reference screen</title>
7
7
 
8
8
  <!-- The kit in one file: tokens + components (this is what @cahyo-dimas/freeday/css resolves to).
9
- Note the file name: dist/freeday.css is components ONLY link that alone and every
9
+ Note the file name: dist/freeday.css is components ONLY. Link that alone and every
10
10
  var(--color-*) / var(--space-*) silently falls back, which looks like a broken stylesheet.
11
11
  Paths are relative, so this works both in the repo and in node_modules/@cahyo-dimas/freeday/docs/. -->
12
12
  <link rel="stylesheet" href="../dist/freeday.bundle.css">
@@ -18,17 +18,17 @@
18
18
  <body>
19
19
 
20
20
  <!-- ══════════════════════════════════════════════════════════════════════════════════════════
21
- REFERENCE SCREEN one business screen, assembled the intended way.
21
+ REFERENCE SCREEN: one business screen, assembled the intended way.
22
22
 
23
23
  Structure, outermost to innermost:
24
24
  .fdy-app the shell (topbar · sidebar · main)
25
- └ .fdy-page the screen body vertical section rhythm
25
+ └ .fdy-page the screen body, vertical section rhythm
26
26
  ├ .fdy-page__header eyebrow + page title + desc | ONE primary action
27
- ├ .fdy-page-section KPI strip (.fdy-stats deliberately not cards)
27
+ ├ .fdy-page-section KPI strip (.fdy-stats, deliberately not cards)
28
28
  ├ .fdy-page-section chart
29
29
  └ .fdy-page-section the data table
30
30
 
31
- Every class here exists in COMPONENTS.md. Every value comes from a token search this file
31
+ Every class here exists in COMPONENTS.md. Every value comes from a token. Search this file
32
32
  for "#" or "px" and you will find none outside the demo-only sizing on the chart wrappers.
33
33
  Doctrine behind the choices: USAGE.md.
34
34
  ══════════════════════════════════════════════════════════════════════════════════════════ -->
@@ -71,7 +71,7 @@
71
71
  </aside>
72
72
 
73
73
  <!-- __content is the column wrapper holding the topbar + main. It exists so the sticky topbar
74
- has a tall containing block to travel in a bare flex cell cannot do that. -->
74
+ has a tall containing block to travel in, which a bare flex cell cannot give it. -->
75
75
  <div class="fdy-app__content">
76
76
 
77
77
  <header class="fdy-app__topbar">
@@ -91,7 +91,7 @@
91
91
  </div>
92
92
 
93
93
  <span class="fdy-badge-wrap">
94
- <button class="fdy-btn fdy-btn--ghost fdy-btn--icon" type="button" aria-label="Notifications 3 new">
94
+ <button class="fdy-btn fdy-btn--ghost fdy-btn--icon" type="button" aria-label="Notifications, 3 new">
95
95
  <span class="fdy-btn__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></span>
96
96
  </button>
97
97
  <span class="fdy-badge-ov fdy-badge-ov--danger" aria-hidden="true">3</span>
@@ -117,7 +117,7 @@
117
117
  <!-- ═══ THE SCREEN ═══ -->
118
118
  <div class="fdy-page">
119
119
 
120
- <!-- One page header. One primary action everything else is ghost. -->
120
+ <!-- One page header. One primary action; everything else is ghost. -->
121
121
  <header class="fdy-page__header">
122
122
  <div class="fdy-page__heading">
123
123
  <p class="fdy-eyebrow">Sales</p>
@@ -130,7 +130,7 @@
130
130
  </div>
131
131
  </header>
132
132
 
133
- <!-- KPI strip: .fdy-stat tiles, NOT cards a metric row must not read as a card grid. -->
133
+ <!-- KPI strip: .fdy-stat tiles, NOT cards, because a metric row must not read as a card grid. -->
134
134
  <section class="fdy-page-section" aria-labelledby="kpi-h">
135
135
  <h3 class="fdy-title-section fdy-visually-hidden" id="kpi-h">Key figures</h3>
136
136
  <div class="fdy-stats fdy-stats--boxed">
@@ -301,7 +301,7 @@
301
301
  </div>
302
302
 
303
303
  <!-- Below md: the SAME rows as a flat list. .fdy-list is the flat container the doctrine
304
- asks for a stack of .fdy-card would put a 34px lift under every row.
304
+ asks for. A stack of .fdy-card would put a 34px lift under every row.
305
305
  In a framework app you would not duplicate the markup: hide <FdyTable> below md, feed
306
306
  this list from its `process` event, and drive both from a controlled `pageIndex` with
307
307
  ONE pager rendered outside the table. See COMPONENTS.md §Data table. -->
@@ -339,7 +339,7 @@
339
339
  <!-- A detail region: key-value pairs + a timeline. Flat section, one card each. -->
340
340
  <section class="fdy-page-section" aria-labelledby="detail-h">
341
341
  <div class="fdy-page-section__head">
342
- <h3 class="fdy-title-section" id="detail-h">INV-2042 detail</h3>
342
+ <h3 class="fdy-title-section" id="detail-h">INV-2042 detail</h3>
343
343
  <button class="fdy-btn fdy-btn--text" type="button">Open full record</button>
344
344
  </div>
345
345
  <div class="fdy-card">
@@ -419,10 +419,10 @@
419
419
 
420
420
  <script>
421
421
  // The nav toggle is the kit's now: `data-fdy-app` on the shell is the whole wiring. What used to
422
- // live here handled the two state classes and nothing else no Escape, no focus trap, no inert
423
- // background, no focus restore which is exactly why the behaviour moved into the kit.
422
+ // live here handled the two state classes and nothing else: no Escape, no focus trap, no inert
423
+ // background, no focus restore. That is why the behaviour moved into the kit.
424
424
 
425
- // Theme + density live on <html>, set once never per component.
425
+ // Theme + density live on <html>, set once, never per component.
426
426
  const root = document.documentElement;
427
427
  document.getElementById('toggle-theme').addEventListener('click', () => {
428
428
  root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cahyo-dimas/freeday",
3
- "version": "1.53.0",
4
- "description": "Freeday token-driven, framework-agnostic UI KIT (design source-of-truth).",
3
+ "version": "2.0.0",
4
+ "description": "Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "sideEffects": true,
@@ -92,10 +92,11 @@
92
92
  "scripts": {
93
93
  "build": "node tokens/build.mjs",
94
94
  "test": "node --test",
95
- "test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/cfl-multi.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs",
95
+ "test:browser": "node --test --test-concurrency=3 browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/cfl-multi.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs",
96
96
  "prepack": "node tokens/build.mjs",
97
97
  "version": "node tokens/build.mjs && git add dist",
98
- "typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit"
98
+ "typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit",
99
+ "test:blazor": "dotnet test test/blazor -v quiet --nologo"
99
100
  },
100
101
  "devDependencies": {
101
102
  "@types/react": "^19.2.17",
package/src/base.css CHANGED
@@ -1,4 +1,4 @@
1
- /* Freeday base layer reset ringan + default via token */
1
+ /* Freeday base layer: reset ringan + default via token */
2
2
  *, *::before, *::after { box-sizing: border-box; }
3
3
  html { -webkit-text-size-adjust: 100%; }
4
4
  body {
@@ -16,21 +16,21 @@ a { color: var(--color-primary); }
16
16
  :where(button, input, select, textarea) { font: inherit; }
17
17
  /* Hidden from sight, kept for assistive tech. `clip` hides PAINTING, not LAYOUT: the box still
18
18
  * has a position, and because it is position:absolute its containing block is the nearest
19
- * POSITIONED ancestor with none, the initial containing block (the document itself).
19
+ * POSITIONED ancestor, and with none, the initial containing block (the document itself).
20
20
  *
21
21
  * That matters, because `overflow` only clips a descendant whose containing block is inside the
22
22
  * overflow box. So a hidden span in a horizontally scrolling table (the kit's own recommended way
23
- * to name an icon button) parks at its static position possibly thousands of px to the right —
23
+ * to name an icon button) parks at its static position, possibly thousands of px to the right —
24
24
  * and drags the whole DOCUMENT sideways: invisible, and immune to `overflow-x:hidden` on every
25
25
  * wrapper. Measured before the fix: 1351px of phantom page scroll from 11 spans.
26
26
  *
27
- * The fix is not here it is on the containers: every clipping/scrolling container in the kit
27
+ * The fix is not here but on the containers: every clipping/scrolling container in the kit
28
28
  * that holds consumer markup declares `position:relative` so it becomes the containing block for
29
29
  * its own out-of-flow content (see test/css.test.mjs, which will not let a new one skip it).
30
- * Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not
30
+ * Note the measurement trap: documentElement.scrollWidth sees it, body.scrollWidth does not,
31
31
  * the honest check is `window.scrollTo(9999, 0)` then reading `window.scrollX`. */
32
32
  .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; }
33
- /* Opt-in list reset. base.css is intentionally a *light* reset it does NOT strip list/paragraph
33
+ /* Opt-in list reset. base.css is intentionally a *light* reset: it does NOT strip list/paragraph
34
34
  * margins (see the kit's own list components, which each reset themselves). If you run a utility
35
35
  * framework with its preflight OFF (the recommended pairing), a semantic <ul>/<ol> keeps native
36
36
  * bullets + a 40px indent; add this class (or a Freeday list component) to normalise it. */
@@ -1,7 +1,7 @@
1
- /* Freeday Accordion (native <details>/<summary>, zero-JS) */
2
- /* position:relative containing block for out-of-flow panel content (see base.css). NOT redundant
1
+ /* Freeday. Accordion (native <details>/<summary>, zero-JS) */
2
+ /* position:relative, containing block for out-of-flow panel content (see base.css). NOT redundant
3
3
  with the panel's reveal animation: that animation gives the panel a transform (which happens to
4
- make it a containing block too), but it lives behind prefers-reduced-motion:no-preference so
4
+ make it a containing block too), but it lives behind prefers-reduced-motion:no-preference, so
5
5
  without this line the escape bug appears ONLY for readers who asked for reduced motion. */
6
6
  .fdy-accordion{position:relative;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);}
7
7
  .fdy-accordion__item + .fdy-accordion__item{border-top:var(--bw) solid var(--color-border);}
@@ -13,5 +13,5 @@
13
13
  .fdy-accordion__item > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
14
14
  .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);}
15
15
  /* Content reveal on open (fade + rise). Native <details> can't animate its own height cross-
16
- * browser without JS, so we animate the revealed panel instead cheap, safe, reduced-motion gated. */
16
+ * browser without JS, so we animate the revealed panel instead, cheap, safe, reduced-motion gated. */
17
17
  @media (prefers-reduced-motion:no-preference){.fdy-accordion__item[open] > .fdy-accordion__panel{animation:fdy-combo-in var(--dur-base) var(--ease-standard);}}
@@ -1,4 +1,4 @@
1
- /* Freeday Alert (inline) */
1
+ /* Freeday. Alert (inline) */
2
2
  .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);}
3
3
  .fdy-alert__icon{flex:none;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin-top:.05rem;}
4
4
  .fdy-alert__icon svg{width:100%;height:100%;}
@@ -1,14 +1,14 @@
1
- /* Freeday App Shell.
1
+ /* Freeday. App Shell.
2
2
  * Natural document scroll: the shell grows with content and the PAGE scrolls,
3
3
  * while the sidebar and topbar are position:sticky so they stay in view. No
4
4
  * fixed-height / internal-scroll trickery (that leaves a white gap if the host
5
5
  * page can still scroll). Structure: .fdy-app > (.fdy-app__sidebar, .fdy-app__content
6
6
  * > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
7
- * topbar a tall containing block so it can travel a bare grid cell can't. */
7
+ * topbar a tall containing block so it can travel, a bare grid cell can't. */
8
8
  .fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
9
9
  .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);}
10
10
  .fdy-app__content{flex:1;min-width:0;display:flex;flex-direction:column;}
11
- /* Brand is a fixed header same height as the topbar so they align, and only
11
+ /* Brand is a fixed header, same height as the topbar so they align, and only
12
12
  * the nav below it scrolls. Bare text (no children) renders as one bold
13
13
  * display line via the rules on .fdy-app__brand itself. For a mark + two-line
14
14
  * title/subtitle, add structured children mirroring .fdy-appbar__brand:
@@ -24,10 +24,10 @@
24
24
  .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;}
25
25
  .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;}
26
26
  .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);}
27
- /* Horizontal variant the SAME nav links laid out as a row, for a top-nav application (one that
27
+ /* Horizontal variant, the SAME nav links laid out as a row, for a top-nav application (one that
28
28
  * puts its primary navigation in `.fdy-appbar` / `.fdy-app__topbar` and has no sidebar). Deliberately
29
29
  * a modifier and not a new block: the item, its states and `aria-current="page"` are unchanged. That
30
- * is the point a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
30
+ * is the point, a navigation link is a link marked `aria-current`, never `aria-selected` (invalid
31
31
  * ARIA on an anchor) and never a tab role, so borrowing `.fdy-tabs` for routes half-adopts a contract
32
32
  * (roving tabindex, a panel per tab) that route links do not honour. position:relative because the
33
33
  * row scrolls; see base.css on why a scroller must be a containing block. */
@@ -39,7 +39,7 @@
39
39
  .fdy-nav__item[aria-current="page"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
40
40
  .fdy-nav__item:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
41
41
  /* Sidebar-menu extras: per-node icon (leading), count badge (trailing), and a
42
- * text label that truncates. Any of these are optional a bare text item works. */
42
+ * text label that truncates. Any of these are optional, a bare text item works. */
43
43
  .fdy-nav__icon{flex:none;display:inline-flex;width:1.15rem;height:1.15rem;}
44
44
  .fdy-nav__icon svg{display:block;width:100%;height:100%;}
45
45
  .fdy-nav__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
@@ -49,7 +49,7 @@
49
49
  .fdy-nav__group{margin:0;}
50
50
  .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);}
51
51
  /* Prose, and a control name: this is the <summary> of a nav group, at --text-xs uppercase. It is
52
- the second class found spending the decorative ink on type (after .fdy-eyebrow) and it was
52
+ the second class found spending the decorative ink on type (after .fdy-eyebrow), and it was
53
53
  already brightening to --color-text-muted on hover, which is the rule admitting its resting
54
54
  state was too faint to read. */
55
55
  .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);}
@@ -58,7 +58,7 @@
58
58
  .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);}
59
59
  .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;}
60
60
  .fdy-nav__group[open] > .fdy-nav__grouplabel::after{transform:rotate(45deg);}
61
- /* --flat: calmer, non-collapsible grouped nav hide the caret, drop the
61
+ /* --flat: calmer, non-collapsible grouped nav, hide the caret, drop the
62
62
  * inter-group divider and the collapse cursor. Works on native <details> groups
63
63
  * (caret just hidden) and on static <div>/<p> groups (never collapsible). */
64
64
  .fdy-nav--flat .fdy-nav__grouplabel::after{display:none;}
@@ -71,7 +71,7 @@
71
71
  .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;}
72
72
  .fdy-nav__tree[open] > summary .fdy-nav__caret{transform:rotate(45deg);}
73
73
  .fdy-nav__sub{display:flex;flex-direction:column;gap:2px;padding-left:calc(var(--space-3) + 1.15rem);margin-top:2px;}
74
- /* Collapse toggle (hamburger) sits in the topbar; collapses the sidebar. */
74
+ /* Collapse toggle (hamburger), sits in the topbar; collapses the sidebar. */
75
75
  .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);}
76
76
  .fdy-app__navtoggle:hover{background:var(--color-surface-2);color:var(--color-text);}
77
77
  .fdy-app__navtoggle:focus-visible{outline:none;box-shadow:0 0 0 2px var(--color-primary);}
@@ -84,7 +84,7 @@
84
84
  .fdy-app__main{flex:1;padding:var(--space-8);}
85
85
  .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);}
86
86
  .fdy-skip:focus{transform:translateY(0);}
87
- /* --static: for a bounded/embedded shell (a demo box, not the whole page)
87
+ /* --static: for a bounded/embedded shell (a demo box, not the whole page),
88
88
  * drop sticky so nothing escapes the box; sidebar stretches to fill it. */
89
89
  .fdy-app--static .fdy-app__sidebar{position:static;height:auto;align-self:stretch;}
90
90
  .fdy-app--static .fdy-app__topbar{position:static;}
@@ -1,4 +1,4 @@
1
- /* Freeday App bar (standalone top bar: brand · actions · spacer).
1
+ /* Freeday. App bar (standalone top bar: brand · actions · spacer).
2
2
  * Distinct from .fdy-app__topbar (which is bound to the shell grid). Use anywhere;
3
3
  * sticky / elevated / dense / coloured via modifiers. Pure CSS. */
4
4
  .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);}
@@ -11,14 +11,14 @@
11
11
  .fdy-appbar__spacer{flex:1;}
12
12
  .fdy-appbar__actions{display:inline-flex;align-items:center;gap:var(--space-1);}
13
13
  /* Primary navigation inside the bar: `<nav class="fdy-nav fdy-nav--horizontal">` with
14
- `.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own a nav link
14
+ `.fdy-nav__item` links (see app-shell.css). The bar ships no link class of its own, a nav link
15
15
  is the same component whether the nav is a sidebar column or a top row. */
16
16
  /* Controls on a coloured app bar go on-colour (icons stay legible, hover is a light wash) */
17
17
  .fdy-appbar--primary .fdy-btn--ghost{background:transparent;border-color:transparent;color:var(--color-on-primary);box-shadow:none;}
18
18
  .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;}
19
19
  /* Nav links on a coloured bar: full on-colour ink in every state (a dimmed variant would trade away
20
20
  the contrast the bar's own token pair guarantees). The current page is carried by the background
21
- wash plus the semibold weight the base rule already applies and by `aria-current` itself. */
21
+ wash plus the semibold weight the base rule already applies, and by `aria-current` itself. */
22
22
  .fdy-appbar--primary .fdy-nav__item{color:var(--color-on-primary);}
23
23
  .fdy-appbar--primary .fdy-nav__item:hover{background:color-mix(in srgb,var(--color-on-primary) 16%,transparent);color:var(--color-on-primary);}
24
24
  .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);}
@@ -1,4 +1,4 @@
1
- /* Freeday Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
1
+ /* Freeday. Autocomplete (text input + filtered suggestion listbox, APG editable combobox).
2
2
  * Enhanced by freeday-autocomplete.js. */
3
3
  .fdy-autocomplete{position:relative;display:block;max-width:22rem;}
4
4
  .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);}
@@ -1,17 +1,17 @@
1
- /* Freeday Avatar */
1
+ /* Freeday. Avatar */
2
2
  .fdy-avatar{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.5rem;height:2.5rem;border-radius:var(--radius-full);background:var(--color-primary-soft);color:var(--color-primary-strong);font-family:var(--font-body);font-weight:var(--weight-semibold);font-size:var(--text-sm);line-height:1;overflow:hidden;user-select:none;}
3
3
  .fdy-avatar img{width:100%;height:100%;object-fit:cover;}
4
4
  /* --xs exists for an avatar INSIDE a control: `.fdy-btn--sm` is exactly 2rem tall, which is also
5
5
  `--sm`'s size, so a small avatar in a small button fills it edge to edge and the ghost border
6
6
  crosses the circle. 1.5rem leaves the same ~4px breathing room the kit's controls give their
7
- icons. Uses `--text-xs` (the smallest type token the kit has there is no `2xs`). */
7
+ icons. Uses `--text-xs` (the smallest type token the kit has, there is no `2xs`). */
8
8
  .fdy-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--text-xs);}
9
9
  .fdy-avatar--sm{width:2rem;height:2rem;font-size:var(--text-xs);}
10
10
  .fdy-avatar--lg{width:3.5rem;height:3.5rem;font-size:var(--text-lg);}
11
11
  .fdy-avatar-group{display:inline-flex;}
12
12
  .fdy-avatar-group .fdy-avatar{border:2px solid var(--color-surface);margin-left:calc(var(--space-2)*-1);}
13
13
  .fdy-avatar-group .fdy-avatar:first-child{margin-left:0;}
14
- /* Decorative identity tones a soft tint of a categorical chart colour with a theme-aware,
14
+ /* Decorative identity tones, a soft tint of a categorical chart colour with a theme-aware,
15
15
  * text-leaning foreground so initials keep WCAG AA (verified >=4.5:1 in light & dark by
16
16
  * test/contrast.test.mjs). Use to disambiguate same-initial avatars (e.g. many "PT …" names):
17
17
  * hash a stable 1..8 index off the FULL name in your app, then apply .fdy-avatar--tone-<n>. */
@@ -1,4 +1,4 @@
1
- /* Freeday Badge */
1
+ /* Freeday. Badge */
2
2
  .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);}
3
3
  .fdy-badge--success{color:var(--color-success-strong);background:var(--color-success-soft);border-color:color-mix(in srgb,var(--color-success) 26%,transparent);}
4
4
  .fdy-badge--warning{color:var(--color-warning-strong);background:var(--color-warning-soft);border-color:color-mix(in srgb,var(--color-warning) 26%,transparent);}
@@ -6,12 +6,12 @@
6
6
  .fdy-badge--info{color:var(--color-info-strong);background:var(--color-info-soft);border-color:color-mix(in srgb,var(--color-info) 26%,transparent);}
7
7
  .fdy-badge--outline{background:transparent;border-color:var(--color-border-strong);color:var(--color-text-muted);box-shadow:none;}
8
8
 
9
- /* Categorical tone badges a status vocabulary larger than the semantic palette, from the
9
+ /* Categorical tone badges, a status vocabulary larger than the semantic palette, from the
10
10
  * general --tone-1..8 tokens. Same validated AA formula as .fdy-avatar--tone-* and
11
11
  * .fdy-chip--tone-* (text >=4.5:1 both themes, gated by test/contrast.test.mjs).
12
12
  *
13
13
  * Semantics come first: a state that IS good, bad or waiting takes --success / --danger /
14
- * --warning. These are for the rest the states a workflow distinguishes but a palette of five
14
+ * --warning. These are for the rest, the states a workflow distinguishes but a palette of five
15
15
  * cannot. A back-office document list carrying Draft, Submitted, Approved, Completed, Settled,
16
16
  * Closed, Transferred, InDeclaration, Open and Rejected in ONE column collapsed to three colours,
17
17
  * which is the report this shipped for. */
@@ -25,7 +25,7 @@
25
25
  .fdy-badge--tone-7{--_fdy-badge-tone:var(--tone-7);}
26
26
  .fdy-badge--tone-8{--_fdy-badge-tone:var(--tone-8);}
27
27
 
28
- /* Overlay badge anchors a small count/dot/icon badge to a corner of any element
28
+ /* Overlay badge, anchors a small count/dot/icon badge to a corner of any element
29
29
  * (icon, button, text). Wrap the content in .fdy-badge-wrap; add .fdy-badge-ov as a
30
30
  * sibling. Default is a solid neutral pill; colour + dot + position via modifiers. */
31
31
  .fdy-badge-wrap{position:relative;display:inline-flex;vertical-align:middle;}
@@ -35,7 +35,7 @@
35
35
  /* Solid colours safe for count/text (on-* tokens, AA in light & dark) */
36
36
  .fdy-badge-ov--primary{background:var(--color-primary);color:var(--color-on-primary);}
37
37
  .fdy-badge-ov--danger{background:var(--color-danger-btn);color:var(--color-on-danger);}
38
- /* Dot-oriented accents (for dots plain text on these is not guaranteed AA) */
38
+ /* Dot-oriented accents (for dots, plain text on these is not guaranteed AA) */
39
39
  .fdy-badge-ov--accent{background:var(--color-accent);}
40
40
  .fdy-badge-ov--info{background:var(--color-info);}
41
41
  .fdy-badge-ov--success{background:var(--color-success);}
@@ -1,4 +1,4 @@
1
- /* Freeday Breadcrumb */
1
+ /* Freeday. Breadcrumb */
2
2
  .fdy-breadcrumb{font-size:var(--text-sm);}
3
3
  .fdy-breadcrumb__list{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);list-style:none;margin:0;padding:0;}
4
4
  .fdy-breadcrumb__item{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--color-text-muted);}
@@ -1,15 +1,15 @@
1
- /* Freeday Responsive display utilities (pairs with freeday-breakpoint.js).
1
+ /* Freeday. Responsive display utilities (pairs with freeday-breakpoint.js).
2
2
  * Breakpoint scale (min-width, px): sm 600 · md 960 · lg 1280 · xl 1920.
3
- * NOTE: media-query breakpoints must be literal CSS can't read custom properties
3
+ * NOTE: media-query breakpoints must be literal. CSS can't read custom properties
4
4
  * inside @media, so these px are structural, not stray magic numbers. */
5
5
  .fdy-hidden{display:none !important;}
6
6
 
7
- /* hide-below-X hidden when the viewport is narrower than X (content shows at X and up) */
7
+ /* hide-below-X, hidden when the viewport is narrower than X (content shows at X and up) */
8
8
  @media (max-width:599.98px){.fdy-hide-below-sm{display:none !important;}}
9
9
  @media (max-width:959.98px){.fdy-hide-below-md{display:none !important;}}
10
10
  @media (max-width:1279.98px){.fdy-hide-below-lg{display:none !important;}}
11
11
 
12
- /* hide-above-X hidden when the viewport is X and wider (content shows below X) */
12
+ /* hide-above-X, hidden when the viewport is X and wider (content shows below X) */
13
13
  @media (min-width:600px){.fdy-hide-above-sm{display:none !important;}}
14
14
  @media (min-width:960px){.fdy-hide-above-md{display:none !important;}}
15
15
  @media (min-width:1280px){.fdy-hide-above-lg{display:none !important;}}
@@ -1,4 +1,4 @@
1
- /* Freeday Button */
1
+ /* Freeday. Button */
2
2
  .fdy-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
3
3
  height:var(--control-h);padding:0 var(--space-4);
4
4
  font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-semibold);
@@ -11,7 +11,7 @@
11
11
  .fdy-btn:active{transform:translateY(1px);box-shadow:0 1px 4px -1px color-mix(in srgb,var(--color-primary) 46%,transparent);}
12
12
  .fdy-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none;}
13
13
  /* Pressed / toggle. `aria-pressed` is already the right attribute for a toggle button, so this adds
14
- no class and no new markup contract and it is what makes .fdy-btn-group a complete segmented
14
+ no class and no new markup contract, and it is what makes .fdy-btn-group a complete segmented
15
15
  control: the group joins the borders, this says which segment you are on. Each fill variant
16
16
  defines its OWN pressed look below; a single shared rule cannot work, because a gradient is a
17
17
  background-IMAGE and would blank the ghost variant's background-color. Solid: invert the gradient
@@ -23,12 +23,12 @@
23
23
  .fdy-btn--ghost:hover{transform:none;background:var(--color-primary-soft);border-color:var(--color-primary-border);box-shadow:var(--shadow-1);}
24
24
  .fdy-btn--ghost:active{transform:translateY(1px);}
25
25
 
26
- /* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger` —
27
- which every reader parses as "quiet destructive" rendered a second SOLID button beside Save.
26
+ /* --danger used to be the SOLID treatment and nothing else, so `fdy-btn--ghost fdy-btn--danger`,
27
+ which every reader parses as "quiet destructive", rendered a second SOLID button beside Save.
28
28
  That is a hierarchy defect, not a cosmetic one: the base class is already the one primary per
29
29
  screen. `.fdy-menu__item--danger` has always modelled the quiet form (red text, not a red block);
30
30
  the pairing rules below bring it to the button. They are more specific than this one and come
31
- after it, so this rule needs no :not() guard of its own one mechanism, not two. */
31
+ after it, so this rule needs no :not() guard of its own, one mechanism, not two. */
32
32
  .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);}
33
33
  .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);}
34
34
  .fdy-btn--danger:active{transform:translateY(1px);}
@@ -42,21 +42,21 @@
42
42
  .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);}
43
43
  .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);}
44
44
 
45
- /* Icon inside a button scales with the button's font size (works for text+icon & icon-only) */
45
+ /* Icon inside a button, scales with the button's font size (works for text+icon & icon-only) */
46
46
  .fdy-btn__icon{display:inline-flex;flex:none;}
47
47
  .fdy-btn__icon svg{display:block;width:1.15em;height:1.15em;}
48
48
 
49
- /* Text button lowest emphasis (no fill/border), colored label, subtle hover tint */
49
+ /* Text button, lowest emphasis (no fill/border), colored label, subtle hover tint */
50
50
  .fdy-btn--text{background:none;color:var(--color-primary-strong);border-color:transparent;box-shadow:none;}
51
51
  .fdy-btn--text:hover{transform:none;background:var(--color-primary-soft);box-shadow:none;}
52
52
  .fdy-btn--text:active{transform:translateY(1px);box-shadow:none;}
53
- /* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink) the same pairing
53
+ /* Ghost/text pressed: the kit's selected-surface pair (soft fill + strong ink), the same pairing
54
54
  .fdy-nav__item uses for the current page, and one the AA contrast gate already covers. This is
55
55
  the realistic segmented control: a row of --ghost buttons with exactly one aria-pressed. */
56
56
  .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;}
57
57
  .fdy-btn--ghost[aria-pressed="true"]:hover,.fdy-btn--text[aria-pressed="true"]:hover{background:var(--color-primary-soft);transform:none;box-shadow:none;}
58
58
 
59
- /* Stretched hit area (#007) spread this button's click target over its nearest positioned
59
+ /* Stretched hit area (#007), spread this button's click target over its nearest positioned
60
60
  * ancestor, so "one card, one primary action, one escape hatch" needs no click handler on the
61
61
  * <article>: the real control stays a real <button>/<a> and keeps its keyboard semantics.
62
62
  * `.fdy-card` is already position:relative, so it is the anchor.
@@ -65,19 +65,19 @@
65
65
  * a transformed element becomes the containing block for its own absolutely-positioned descendants,
66
66
  * so `inset:0` re-anchors from the card to the button's own box the moment the button is hovered or
67
67
  * pressed. Measured: mousedown lands on the button, the overlay collapses under the pointer, mouseup
68
- * lands on some other element, and the browser fires `click` on their common ancestor the button
68
+ * lands on some other element, and the browser fires `click` on their common ancestor, the button
69
69
  * never gets one. It demos fine on hover for --text/--ghost (their hover is already transform:none)
70
70
  * and only a real press reveals it; the default and --danger fills break one step earlier, on hover.
71
71
  * Press feedback moves to the card, which is the object actually being pressed (see card.css). */
72
72
  .fdy-btn--stretch::after{content:'';position:absolute;inset:0;}
73
73
  .fdy-btn--stretch:hover,.fdy-btn--stretch:active{transform:none;}
74
74
 
75
- /* Icon-only square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
75
+ /* Icon-only, square; combine with --ghost/--text/--sm/--lg. Requires aria-label. */
76
76
  .fdy-btn--icon{width:var(--control-h);padding:0;}
77
77
  .fdy-btn--icon.fdy-btn--sm{width:calc(var(--control-h) - var(--space-2));}
78
78
  .fdy-btn--icon.fdy-btn--lg{width:calc(var(--control-h) + var(--space-2));}
79
79
 
80
- /* Button group segmented, joined borders, rounded only at the ends */
80
+ /* Button group, segmented, joined borders, rounded only at the ends */
81
81
  .fdy-btn-group{display:inline-flex;}
82
82
  .fdy-btn-group .fdy-btn{border-radius:0;}
83
83
  .fdy-btn-group .fdy-btn:not(:first-child){margin-left:calc(var(--bw) * -1);}
@@ -85,7 +85,7 @@
85
85
  .fdy-btn-group .fdy-btn:first-child{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md);}
86
86
  .fdy-btn-group .fdy-btn:last-child{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md);}
87
87
 
88
- /* FAB circular elevated action button (icon), optional extended pill with label */
88
+ /* FAB, circular elevated action button (icon), optional extended pill with label */
89
89
  .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);}
90
90
  .fdy-fab:hover{transform:translateY(-1px);box-shadow:var(--shadow-4);}
91
91
  .fdy-fab:active{transform:translateY(1px);}
@@ -1,32 +1,32 @@
1
- /* Freeday Card */
2
- /* position:relative containing block for out-of-flow card content (see base.css); it also gives
1
+ /* Freeday. Card */
2
+ /* position:relative, containing block for out-of-flow card content (see base.css); it also gives
3
3
  consumers the anchor a corner ribbon/badge needs. */
4
4
  .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);}
5
5
  .fdy-card--elevated{box-shadow:var(--shadow-lift-hover);}
6
6
  .fdy-card--interactive{cursor:pointer;}
7
7
  .fdy-card--interactive:hover{box-shadow:var(--shadow-lift-hover);transform:translateY(-3px);}
8
- /* Pressing a stretched target (see .fdy-btn--stretch) is pressing the CARD the button's own
8
+ /* Pressing a stretched target (see .fdy-btn--stretch) is pressing the CARD, the button's own
9
9
  nudge had to go, and the object under the pointer is the card. Ordered before the reduced-motion
10
10
  block and the disabled rules so both still win at equal specificity. */
11
11
  .fdy-card--interactive:has(.fdy-btn--stretch:active){transform:translateY(-1px);}
12
12
  /* The global reduced-motion reset kills the ANIMATION; drop the -3px shift too so the card
13
13
  * doesn't jump. The shadow still communicates the affordance. */
14
14
  @media (prefers-reduced-motion:reduce){.fdy-card--interactive:hover,.fdy-card--interactive:has(.fdy-btn--stretch:active){transform:none;}}
15
- /* When the whole card IS the control a real <button> so the row is keyboard-reachable and
16
- * announced reset the UA button box. Note: it does NOT reset background/border, because
15
+ /* When the whole card IS the control, a real <button> so the row is keyboard-reachable and
16
+ * announced, reset the UA button box. Note: it does NOT reset background/border, because
17
17
  * .fdy-card already sets its own surface + border (resetting them here would strip the card). */
18
18
  .fdy-card--button{display:block;width:100%;text-align:inherit;color:inherit;appearance:none;-webkit-appearance:none;}
19
- /* Disabled card-as-control same contract as .fdy-list__row--button: dim + not-allowed, and the
19
+ /* Disabled card-as-control, same contract as .fdy-list__row--button: dim + not-allowed, and the
20
20
  --interactive lift is withdrawn so a refusing control stops answering the pointer. */
21
21
  .fdy-card--button:disabled,.fdy-card--button[aria-disabled="true"],.fdy-card--interactive[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
22
22
  .fdy-card--interactive:disabled:hover,.fdy-card--interactive[aria-disabled="true"]:hover{box-shadow:var(--shadow-lift);transform:none;}
23
- /* Escape hatches stay clickable above a stretched target automatic, not a documented convention,
23
+ /* Escape hatches stay clickable above a stretched target, automatic, not a documented convention,
24
24
  because forgetting it fails SILENTLY: the secondary control looks and hovers exactly as before and
25
25
  simply never receives the click, which is the very symptom the pattern exists to cure. Matched by
26
26
  shape (anything focusable) so a control the kit does not ship yet is covered too. */
27
27
  :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;}
28
28
  /* The position is a DEFAULT, not an override: z-index needs a positioned box (or a flex/grid item),
29
- but an app that deliberately pins a control inside the card a corner dismiss, a favourite star
29
+ but an app that deliberately pins a control inside the card, a corner dismiss, a favourite star,
30
30
  must keep its own `position:absolute`. Wrapped in :where() so this whole selector weighs nothing
31
31
  and ANY app rule wins it; measured, because at normal specificity the kit silently dragged an
32
32
  absolutely positioned dismiss button back into the flow. Do not merge these two rules. */
@@ -1,6 +1,6 @@
1
- /* Freeday Carousel (scroll-snap track + arrows + dots). Enhanced by freeday-carousel.js. */
1
+ /* Freeday. Carousel (scroll-snap track + arrows + dots). Enhanced by freeday-carousel.js. */
2
2
  .fdy-carousel{position:relative;}
3
- /* position:relative the arrows are positioned against .fdy-carousel (the parent), so this only
3
+ /* position:relative means the arrows are positioned against .fdy-carousel (the parent), so this only
4
4
  catches out-of-flow content inside the SLIDES, which would otherwise escape the viewport
5
5
  entirely and scroll the page by one slide-offset per slide (see base.css). */
6
6
  .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;}
@@ -1,7 +1,7 @@
1
- /* Freeday Cascade select (hierarchical drill-down picker).
1
+ /* Freeday. Cascade select (hierarchical drill-down picker).
2
2
  * Built by freeday-cascade.js from a [data-fdy-cascade] wrapper whose hidden
3
3
  * nested <ul> is the data model: a <li> with a child <ul> is a branch, one
4
- * without is a selectable leaf. The popover shows one level at a time branches
4
+ * without is a selectable leaf. The popover shows one level at a time, branches
5
5
  * drill in, a back control ascends, a leaf selects (value = full path).
6
6
  * Trigger visual matches the combo / date / time control family. */
7
7
  .fdy-cascade{position:relative;display:inline-block;width:100%;max-width:22rem;}
@@ -1,13 +1,13 @@
1
- /* Freeday Choose-from-list dialog (searchable master-data picker).
1
+ /* Freeday. Choose-from-list dialog (searchable master-data picker).
2
2
  * Built on .fdy-modal: header + search + scrollable results table + footer.
3
3
  * Backs a choose-from-list field (readonly input + .fdy-input-group__btn magnifier). */
4
4
  .fdy-modal--cfl{width:min(46rem,calc(100vw - var(--space-8)));}
5
5
  .fdy-modal--cfl .fdy-modal__body{padding:0;display:flex;flex-direction:column;min-height:0;overflow:hidden;}
6
6
 
7
- /* Search bar single leading-icon field; Enter/typing filters the results. */
7
+ /* Search bar, single leading-icon field; Enter/typing filters the results. */
8
8
  .fdy-cfl__search{flex:none;padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
9
9
 
10
- /* Results dense rows, sticky header, its own scroll region. */
10
+ /* Results, dense rows, sticky header, its own scroll region. */
11
11
  .fdy-cfl__results{overflow:auto;max-height:min(24rem,50vh);}
12
12
  .fdy-cfl__results .fdy-table th{position:sticky;top:0;z-index:1;}
13
13
  .fdy-cfl__row{cursor:pointer;}
@@ -22,7 +22,7 @@
22
22
  .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;}
23
23
  .fdy-cfl__empty[hidden]{display:none;}
24
24
 
25
- /* Footer selection count (multi) sits left, actions right. */
25
+ /* Footer, selection count (multi) sits left, actions right. */
26
26
  .fdy-modal--cfl .fdy-modal__footer{justify-content:space-between;}
27
27
  .fdy-cfl__count{font-size:var(--text-sm);color:var(--color-text-muted);}
28
28
  .fdy-cfl__actions{display:flex;gap:var(--space-2);}
@@ -1,4 +1,4 @@
1
- /* Freeday Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
1
+ /* Freeday. Charts (zero-dependency CSS/SVG, rendered by freeday-chart.js).
2
2
  * Colour comes from --fdy-chart (a token, set inline by the renderer from data-fdy-color). */
3
3
 
4
4
  /* Sparkline / trend line (SVG) */
@@ -18,7 +18,7 @@
18
18
  /* Hover emphasis: dim all bars while the chart is hovered, keep the hovered one lit. */
19
19
  @media (hover:hover){.fdy-bars:hover .fdy-bars__bar{opacity:.4;}.fdy-bars__col:hover .fdy-bars__bar{opacity:1;}}
20
20
 
21
- /* Interactive tooltip (shared by bar + donut) Chart.js-style hover readout. */
21
+ /* Interactive tooltip (shared by bar + donut). Chart.js-style hover readout. */
22
22
  .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);}
23
23
  .fdy-chart__tip.is-visible{opacity:1;}
24
24
 
@@ -39,7 +39,7 @@
39
39
  /* Horizontal legend for cartesian charts (legend sits above the plot). */
40
40
  .fdy-chart__legend--row{flex-direction:row;flex-wrap:wrap;gap:var(--space-2) var(--space-4);}
41
41
 
42
- /* Cartesian charts (line / area / multi-series & stacked bar) SVG with themed axes.
42
+ /* Cartesian charts (line / area / multi-series & stacked bar). SVG with themed axes.
43
43
  * Axis colour comes from --chart-grid / --chart-tick (both theme-aware); series colours are
44
44
  * set inline by the renderer from --chart-1..8, so the whole chart recolours on data-theme. */
45
45
  .fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100%;--fdy-chart-tick-size:var(--text-xs);}
@@ -1,7 +1,7 @@
1
- /* Freeday Chip (tag / removable filter) */
1
+ /* Freeday. Chip (tag / removable filter) */
2
2
  .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);}
3
3
  .fdy-chip--primary{color:var(--color-primary-strong);background:var(--color-primary-soft);border-color:color-mix(in srgb,var(--color-primary) 22%,transparent);}
4
- /* Categorical tone chips non-semantic category tags / legend swatches, from the general
4
+ /* Categorical tone chips, non-semantic category tags / legend swatches, from the general
5
5
  * --tone-1..8 palette. Same validated AA formula as .fdy-avatar--tone-* (text >=4.5:1 both themes,
6
6
  * gated by test/contrast.test.mjs). */
7
7
  .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));}