@cahyo-dimas/freeday 1.54.0 → 2.1.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 (147) hide show
  1. package/CHANGELOG.md +713 -545
  2. package/COMPONENTS.md +417 -139
  3. package/README.id.md +46 -45
  4. package/README.md +46 -43
  5. package/USAGE.md +34 -34
  6. package/adapters/blazor/FdyAppShell.razor +2 -2
  7. package/adapters/blazor/FdyAppShell.razor.cs +6 -6
  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 +1 -1
  24. package/adapters/core/app-shell.js +6 -6
  25. package/adapters/core/cfl-value.d.ts +11 -0
  26. package/adapters/core/cfl-value.js +31 -0
  27. package/adapters/core/table-model.d.ts +2 -2
  28. package/adapters/core/table-model.js +8 -8
  29. package/adapters/react/components/FdyAppShell.tsx +5 -5
  30. package/adapters/react/components/FdyAutocomplete.tsx +1 -1
  31. package/adapters/react/components/FdyCascade.tsx +1 -1
  32. package/adapters/react/components/FdyCfl.tsx +11 -11
  33. package/adapters/react/components/FdyDateRange.tsx +2 -2
  34. package/adapters/react/components/FdyDatepicker.tsx +5 -5
  35. package/adapters/react/components/FdyDrawer.tsx +1 -1
  36. package/adapters/react/components/FdyModal.tsx +2 -2
  37. package/adapters/react/components/FdyTable.tsx +7 -7
  38. package/adapters/react/components/FdyTableFilter.tsx +1 -1
  39. package/adapters/react/components/FdyTableFooter.tsx +5 -5
  40. package/adapters/react/index.d.ts +4 -1
  41. package/adapters/react/index.js +1 -0
  42. package/adapters/react/useFreeday.js +3 -3
  43. package/adapters/vue/components/FdyAppShell.vue +5 -5
  44. package/adapters/vue/components/FdyAutocomplete.vue +1 -1
  45. package/adapters/vue/components/FdyCascade.vue +1 -1
  46. package/adapters/vue/components/FdyCfl.vue +8 -8
  47. package/adapters/vue/components/FdyChart.vue +2 -2
  48. package/adapters/vue/components/FdyCombo.vue +1 -1
  49. package/adapters/vue/components/FdyDateRange.vue +2 -2
  50. package/adapters/vue/components/FdyDatepicker.vue +5 -5
  51. package/adapters/vue/components/FdyDrawer.vue +3 -3
  52. package/adapters/vue/components/FdyModal.vue +3 -3
  53. package/adapters/vue/components/FdyTable.vue +7 -7
  54. package/adapters/vue/components/FdyTableFilter.vue +1 -1
  55. package/adapters/vue/components/FdyTableFooter.vue +5 -5
  56. package/adapters/vue/index.d.ts +5 -2
  57. package/adapters/vue/index.js +1 -0
  58. package/adapters/vue/useFreeday.js +3 -3
  59. package/dist/asset.d.ts +3 -3
  60. package/dist/freeday-app-shell.js +9 -9
  61. package/dist/freeday-autocomplete.js +2 -2
  62. package/dist/freeday-breakpoint.js +1 -1
  63. package/dist/freeday-carousel.js +8 -7
  64. package/dist/freeday-cascade.js +10 -9
  65. package/dist/freeday-cfl.js +12 -11
  66. package/dist/freeday-chart.js +11 -11
  67. package/dist/freeday-chip.js +3 -3
  68. package/dist/freeday-datepicker.js +11 -7
  69. package/dist/freeday-datetime.js +5 -5
  70. package/dist/freeday-drawer.js +1 -1
  71. package/dist/freeday-form.js +17 -16
  72. package/dist/freeday-mask.js +9 -8
  73. package/dist/freeday-menu.js +1 -1
  74. package/dist/freeday-number.js +3 -3
  75. package/dist/freeday-popover.js +3 -3
  76. package/dist/freeday-rating.js +2 -2
  77. package/dist/freeday-select.js +5 -5
  78. package/dist/freeday-slider.js +2 -2
  79. package/dist/freeday-stepper.js +10 -9
  80. package/dist/freeday-table.js +18 -17
  81. package/dist/freeday-tabs.js +1 -1
  82. package/dist/freeday-timepicker.js +2 -2
  83. package/dist/freeday-toast.js +11 -10
  84. package/dist/freeday-tree.js +3 -3
  85. package/dist/freeday-upload.js +28 -27
  86. package/dist/freeday.bundle.css +176 -176
  87. package/dist/freeday.css +170 -170
  88. package/dist/freeday.d.ts +2 -2
  89. package/dist/freeday.js +189 -176
  90. package/dist/freeday.tokens.css +5 -5
  91. package/docs/agent-onboarding.md +49 -46
  92. package/docs/getting-started.md +49 -40
  93. package/docs/integrations.md +18 -18
  94. package/docs/reference-screen.html +14 -14
  95. package/package.json +4 -3
  96. package/src/base.css +6 -6
  97. package/src/components/accordion.css +4 -4
  98. package/src/components/alert.css +1 -1
  99. package/src/components/app-shell.css +10 -10
  100. package/src/components/appbar.css +3 -3
  101. package/src/components/autocomplete.css +1 -1
  102. package/src/components/avatar.css +3 -3
  103. package/src/components/badge.css +5 -5
  104. package/src/components/breadcrumb.css +1 -1
  105. package/src/components/breakpoints.css +4 -4
  106. package/src/components/button.css +13 -13
  107. package/src/components/card.css +8 -8
  108. package/src/components/carousel.css +2 -2
  109. package/src/components/cascade.css +2 -2
  110. package/src/components/cfl.css +4 -4
  111. package/src/components/chart.css +3 -3
  112. package/src/components/chip.css +2 -2
  113. package/src/components/combo.css +3 -3
  114. package/src/components/composition.css +7 -7
  115. package/src/components/datepicker.css +6 -6
  116. package/src/components/datetimepicker.css +1 -1
  117. package/src/components/description-list.css +1 -1
  118. package/src/components/divider.css +1 -1
  119. package/src/components/drawer.css +1 -1
  120. package/src/components/file-upload.css +5 -5
  121. package/src/components/filterbar.css +7 -7
  122. package/src/components/form-grid.css +2 -2
  123. package/src/components/input-group.css +6 -6
  124. package/src/components/input.css +6 -6
  125. package/src/components/kbd.css +1 -1
  126. package/src/components/list.css +6 -6
  127. package/src/components/menu.css +3 -3
  128. package/src/components/modal.css +1 -1
  129. package/src/components/pagination.css +1 -1
  130. package/src/components/progress.css +1 -1
  131. package/src/components/rating.css +2 -2
  132. package/src/components/selection.css +4 -4
  133. package/src/components/skeleton.css +1 -1
  134. package/src/components/slider.css +1 -1
  135. package/src/components/spinner.css +1 -1
  136. package/src/components/states.css +1 -1
  137. package/src/components/stepper.css +2 -2
  138. package/src/components/table.css +15 -15
  139. package/src/components/tabs.css +3 -3
  140. package/src/components/timeline.css +1 -1
  141. package/src/components/timepicker.css +1 -1
  142. package/src/components/toast.css +3 -3
  143. package/src/components/tooltip.css +1 -1
  144. package/src/components/tree.css +2 -2
  145. package/tokens/breakpoints.d.ts +2 -2
  146. package/tokens/breakpoints.mjs +3 -3
  147. package/tokens/tokens.json +1 -1
@@ -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));}
@@ -1,11 +1,11 @@
1
- /* Freeday — Combobox (custom styled select, WAI-ARIA APG select-only) */
1
+ /* Freeday. Combobox (custom styled select, WAI-ARIA APG select-only) */
2
2
  .fdy-combo{position:relative;display:block;max-width:22rem;}
3
3
  .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);}
4
4
  .fdy-combo__button:hover{border-color:var(--color-text-muted);}
5
5
  .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);}
6
6
  .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);}
7
7
  .fdy-combo__button:disabled,.fdy-combo__button[aria-disabled="true"]{opacity:.5;cursor:not-allowed;}
8
- /* Read-only: stays focusable & shows its value at full contrast but can't open — muted surface,
8
+ /* Read-only: stays focusable & shows its value at full contrast but can't open, muted surface,
9
9
  softened border, default cursor, no focus-ring escalation (matches input[readonly], #39). */
10
10
  .fdy-combo__button[aria-readonly="true"]{background:var(--color-surface-2);border-color:var(--color-border);cursor:default;}
11
11
  .fdy-combo__button[aria-readonly="true"]:hover{border-color:var(--color-border);}
@@ -29,7 +29,7 @@
29
29
  .fdy-combo__option.is-highlighted[aria-selected="true"]{box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 55%,transparent);}
30
30
  /* The tick is CSS, not content (#004 §2). Inside role="option" it joined the option's ACCESSIBLE
31
31
  NAME, so the selected option announced "✓August" while every other announced "August": the state
32
- was read twice — once as aria-selected, once as decoration — and a test asking for an option by
32
+ was read twice, once as aria-selected, once as decoration, and a test asking for an option by
33
33
  its own label stopped matching the one that was selected. The `/ ""` is alt text: the glyph is
34
34
  painted, the accessibility tree gets nothing. Same reasoning as .fdy-label--required (1.29.0).
35
35
  The empty box still reserves its width, so labels stay aligned whether selected or not. */
@@ -1,8 +1,8 @@
1
- /* Freeday — Page composition & type roles.
1
+ /* Freeday. Page composition & type roles.
2
2
  *
3
3
  * The kit stops at the component; these primitives encode *how a page is assembled* so screens
4
4
  * built by different people cohere. They carry the usage doctrine (see USAGE.md) in markup: a
5
- * named type role gets used, a doc gets skimmed. Everything here is token-driven layout — no new
5
+ * named type role gets used, a doc gets skimmed. Everything here is token-driven layout, no new
6
6
  * colours, no new shadows. Pair with .fdy-app (the shell) and the components inside.
7
7
  */
8
8
 
@@ -11,7 +11,7 @@
11
11
  * page title (the mistake that collapses three levels into one grey mush). */
12
12
  /* --color-text-muted, not -subtle: an eyebrow is a small label naming the thing below it, i.e.
13
13
  prose. -subtle is gated at 3.0 on purpose (placeholders, separators, decorative glyphs) and
14
- measures 4.41 on the page ground — axe reports it as a serious color-contrast failure. Same
14
+ measures 4.41 on the page ground, axe reports it as a serious color-contrast failure. Same
15
15
  argument, same ink, same 4.41 as .fdy-stat__label in 1.30.0. */
16
16
  .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;}
17
17
  .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;}
@@ -45,19 +45,19 @@
45
45
  .fdy-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:var(--space-5);}
46
46
  .fdy-stat{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
47
47
  /* --color-text-muted, not -subtle: this is a LABEL, i.e. text, and -subtle is deliberately gated at
48
- 3.0 (placeholders, dividers, decorative glyphs). On surface-2 -subtle measures 4.41 — under AA
48
+ 3.0 (placeholders, dividers, decorative glyphs). On surface-2 -subtle measures 4.41, under AA
49
49
  for text, and a stat label is often the only thing naming the number above it. */
50
50
  .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);}
51
51
  .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;}
52
52
  .fdy-stat__value small{font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text-muted);}
53
53
  .fdy-stat__meta{font-size:var(--text-sm);color:var(--color-text-muted);display:flex;align-items:center;gap:var(--space-2);}
54
- /* A stat value is display type sized for a COUNT — "1,284" — and a back office puts money in it.
54
+ /* A stat value is display type sized for a COUNT, "1,284", and a back office puts money in it.
55
55
  "IDR 300,000.00" needs 224px at --text-3xl and the grid's own track is 11rem, so it wrapped
56
56
  between the currency and the number: two lines of 31px where the design says one. Measured, not
57
57
  guessed (#020).
58
58
  The value shrinks only when its own column is too narrow to hold it, so a wide dashboard keeps
59
59
  exactly the type it has today. Scoped to a stat INSIDE .fdy-stats, where the width comes from the
60
- grid track — `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
60
+ grid track, `container-type:inline-size` on a standalone .fdy-stat would stop it sizing to its
61
61
  own content. The unconditional rule above stays as the fallback wherever @container is missing. */
62
62
  @supports (container-type:inline-size){
63
63
  .fdy-stats>.fdy-stat{container-type:inline-size;}
@@ -70,7 +70,7 @@
70
70
  /* Icon box (#001 §4). The kit ships no icons on purpose, but every icon slot it DOES ship
71
71
  (.fdy-btn__icon, .fdy-nav__icon, .fdy-state__icon) assumes something sensible inside, so a
72
72
  standalone icon had no contract and every app invented one. 1em keeps the glyph matched to the
73
- text beside it at every scale — bring your own paths, use our box. Works on the <svg> itself or
73
+ text beside it at every scale, bring your own paths, use our box. Works on the <svg> itself or
74
74
  on a wrapper around it. */
75
75
  .fdy-icon{display:inline-block;width:1em;height:1em;flex:none;vertical-align:-0.125em;}
76
76
  .fdy-icon>svg{display:block;width:100%;height:100%;}
@@ -1,10 +1,10 @@
1
- /* Freeday — Date picker (input-styled trigger + calendar popover).
1
+ /* Freeday. Date picker (input-styled trigger + calendar popover).
2
2
  * Built by freeday-datepicker.js from an empty [data-fdy-datepicker] wrapper. */
3
3
  .fdy-datepicker{position:relative;display:inline-block;width:100%;max-width:14rem;}
4
4
  /* Inside a labelled field, a picker fills the field like every other control.
5
5
  The 14rem/11rem caps above suit a picker standing on its own; in a form grid
6
- they leave the date box visibly short of the combo beside it — 224px against
7
- 319px in a two-column layout — and the row reads as ragged. The field's own
6
+ they leave the date box visibly short of the combo beside it. 224px against
7
+ 319px in a two-column layout, and the row reads as ragged. The field's own
8
8
  max-width is what should govern there. */
9
9
  .fdy-field>.fdy-datepicker,.fdy-field>.fdy-timepicker,.fdy-field>.fdy-daterange{max-width:none;}
10
10
  .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);}
@@ -50,12 +50,12 @@
50
50
  .fdy-cal__nav:hover{background:var(--color-surface-2);color:var(--color-text);}
51
51
  .fdy-cal__nav:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
52
52
  .fdy-cal__grid{display:grid;grid-template-columns:repeat(7,2rem);gap:2px;}
53
- /* Month grid — 3×4 over the same track width as the 7-column day grid, so the panel does not resize
53
+ /* Month grid. 3×4 over the same track width as the 7-column day grid, so the panel does not resize
54
54
  when you drill and the pointer does not have to travel.
55
55
 
56
56
  AFTER `.fdy-cal__grid`, and that is the whole rule: the element carries both classes, they weigh
57
57
  the same, so source order decides. Declared earlier, the modifier lost to the base it modifies and
58
- twelve months rendered seven across — the layout the comment above described, never once applied. */
58
+ twelve months rendered seven across, the layout the comment above described, never once applied. */
59
59
  .fdy-cal__grid--months,.fdy-cal__grid--years{grid-template-columns:repeat(3,1fr);gap:var(--space-1);}
60
60
  .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;}
61
61
  .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;}
@@ -73,7 +73,7 @@
73
73
  /* Responsive: each picker may shrink (min-width:0) & wrap so the range never overflows a
74
74
  narrow row/phone; inline-flex still hugs to ~23rem at wider widths (look unchanged). */
75
75
  .fdy-daterange{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);}
76
- /* Each picker shrinks from an 11rem basis but not below ~7rem — enough for "dd/mm/yyyy" + the icon,
76
+ /* Each picker shrinks from an 11rem basis but not below ~7rem, enough for "dd/mm/yyyy" + the icon,
77
77
  so a squeezed range compresses gracefully (and wraps) instead of clipping the date. */
78
78
  .fdy-daterange .fdy-datepicker{flex:0 1 11rem;min-width:7rem;}
79
79
  .fdy-daterange__sep{flex:none;color:var(--color-text-subtle);}
@@ -1,4 +1,4 @@
1
- /* Freeday — Datetime picker (composed: date picker + time picker → one value).
1
+ /* Freeday. Datetime picker (composed: date picker + time picker → one value).
2
2
  * A thin composer (freeday-datetime.js) links a child [data-fdy-datepicker] and
3
3
  * [data-fdy-timepicker] and emits a single combined event. Icon variants come from
4
4
  * the child pickers (data-fdy-no-icon / <template data-fdy-icon>). */
@@ -1,4 +1,4 @@
1
- /* Freeday — Description list (key-value detail view) */
1
+ /* Freeday. Description list (key-value detail view) */
2
2
  .fdy-dl{display:grid;grid-template-columns:auto 1fr;gap:var(--space-2) var(--space-5);margin:0;}
3
3
  .fdy-dl dt{color:var(--color-text-muted);font-size:var(--text-sm);}
4
4
  .fdy-dl dd{margin:0;color:var(--color-text);font-size:var(--text-sm);font-weight:var(--weight-medium);}
@@ -1,3 +1,3 @@
1
- /* Freeday — Divider */
1
+ /* Freeday. Divider */
2
2
  .fdy-divider{border:0;border-top:var(--bw) solid var(--color-border);margin:var(--space-4) 0;}
3
3
  .fdy-divider--vertical{border-top:0;border-left:var(--bw) solid var(--color-border);margin:0 var(--space-4);align-self:stretch;}
@@ -1,4 +1,4 @@
1
- /* Freeday — Drawer (temporary side panel on native <dialog>: focus-trap / Esc /
1
+ /* Freeday. Drawer (temporary side panel on native <dialog>: focus-trap / Esc /
2
2
  * backdrop / return-focus are free). Anchors left (default) or right.
3
3
  * Enhanced by freeday-drawer.js (open trigger + backdrop-click close). */
4
4
  /* display is gated behind [open] so the native <dialog> stays hidden until opened.
@@ -1,4 +1,4 @@
1
- /* Freeday — File upload (drop target + explicit per-file states).
1
+ /* Freeday. File upload (drop target + explicit per-file states).
2
2
  * Enhanced by freeday-upload.js; states: idle · drag-over · uploading · done · error. */
3
3
  .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);}
4
4
  .fdy-dropzone:hover{border-color:var(--color-text-muted);}
@@ -10,17 +10,17 @@
10
10
  .fdy-dropzone__icon svg{display:block;width:2rem;height:2rem;}
11
11
  .fdy-dropzone__title{font-size:inherit;font-weight:var(--weight-medium);color:var(--color-text);margin:0;}
12
12
  .fdy-dropzone__hint{font-size:var(--text-xs);}
13
- /* Wide (horizontal) layout — icon beside the copy instead of stacked, for full-width
13
+ /* Wide (horizontal) layout, icon beside the copy instead of stacked, for full-width
14
14
  * sections. Wrap the title + hint in .fdy-dropzone__text. */
15
15
  .fdy-dropzone--row{flex-direction:row;align-items:center;justify-content:center;text-align:left;gap:var(--space-4);}
16
16
  .fdy-dropzone__text{display:flex;flex-direction:column;gap:var(--space-1);}
17
17
 
18
18
  /* File rows (list of added/uploaded/rejected files) */
19
- /* Symmetric on purpose. The list carried margin-top and nothing below, so whatever follows it —
20
- an "Add files" button, in every upload UI there is — sat flush against the last row: measured
19
+ /* Symmetric on purpose. The list carried margin-top and nothing below, so whatever follows it,
20
+ an "Add files" button, in every upload UI there is, sat flush against the last row: measured
21
21
  0px. A block that claims space on one side only is not spacing, it is a lean. */
22
22
  .fdy-filelist{display:flex;flex-direction:column;gap:var(--space-2);margin-top:var(--space-3);margin-bottom:var(--space-3);}
23
- /* Nothing after it, nothing to separate from — so the room is not spent. */
23
+ /* Nothing after it, nothing to separate from, so the room is not spent. */
24
24
  .fdy-filelist:last-child{margin-bottom:0;}
25
25
  /* Side-by-side rows on wide screens (reflow to one column when narrow). */
26
26
  .fdy-filelist--grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));}
@@ -1,4 +1,4 @@
1
- /* Freeday — Filter bar (a consistent row of filters: search + selects + actions).
1
+ /* Freeday. Filter bar (a consistent row of filters: search + selects + actions).
2
2
  * Solves the "ragged filter row": apps improvised flex rows with ad-hoc
3
3
  * flex/min-width, so grow fields ballooned while selects stayed tiny and no two
4
4
  * screens matched. Here every field snaps to a shared width rhythm; one field may
@@ -7,7 +7,7 @@
7
7
  .fdy-filterbar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-3) var(--space-4);}
8
8
  /* Composed with a table toolbar (one element, both classes): the fields decide the alignment.
9
9
  * `.fdy-table-toolbar` sets align-items:center (right for bare buttons) at equal specificity, and
10
- * being later in the bundle it would otherwise win — floating labelled fields' neighbours out of line.
10
+ * being later in the bundle it would otherwise win, floating labelled fields' neighbours out of line.
11
11
  * This 2-class selector (0,2,0) settles it toward the filterbar's flex-end regardless of source order. */
12
12
  .fdy-table-toolbar.fdy-filterbar{align-items:flex-end;}
13
13
  /* Fields are direct children. Drop the standalone field's 22rem cap; default to the
@@ -15,7 +15,7 @@
15
15
  .fdy-filterbar>.fdy-field{max-width:none;flex:0 0 auto;width:12rem;}
16
16
  /* Controls fill their field's column (drop the standalone input-group/combo caps). */
17
17
  .fdy-filterbar>.fdy-field .fdy-input-group,.fdy-filterbar>.fdy-field .fdy-combo{max-width:none;width:100%;}
18
- /* Column-width rhythm — pick one per field so bars across the app line up. */
18
+ /* Column-width rhythm, pick one per field so bars across the app line up. */
19
19
  .fdy-filterbar>.fdy-field--w-sm{width:9rem;}
20
20
  .fdy-filterbar>.fdy-field--w-lg{width:15rem;}
21
21
  .fdy-filterbar>.fdy-field--w-xl{width:18rem;}
@@ -25,11 +25,11 @@
25
25
  .fdy-filterbar>.fdy-field--w-grow{flex:1 1 16rem;width:auto;min-width:12rem;}
26
26
  /* Trailing actions (buttons) pushed to the end, aligned with the controls' baseline.
27
27
  * Wrap note: a wrapped bar breaks flex lines on each item's *flex-basis* (the grow field's
28
- * `flex:1 1 16rem` reserves 16rem for line-breaking), NOT its rendered width — so the grow basis,
28
+ * `flex:1 1 16rem` reserves 16rem for line-breaking), NOT its rendered width, so the grow basis,
29
29
  * not the visible width, decides the wrap point. Margins can't keep the actions off a lonely wrapped
30
30
  * row; to avoid stranded actions, tune the --w-grow basis or drop a field so the bar fits one row. */
31
- /* A control that carries NO label of its own — a checkbox, a switch, a lone
32
- button — in a bar whose `align-items:flex-end` exists to line up LABELLED
31
+ /* A control that carries NO label of its own, a checkbox, a switch, a lone
32
+ button, in a bar whose `align-items:flex-end` exists to line up LABELLED
33
33
  fields. Flex-end puts such a control's bottom on the inputs' bottom, so a
34
34
  20px checkbox sits well below the 32px input's centre line and reads as
35
35
  dropped. Giving it the height of a control lands its box exactly where an
@@ -37,7 +37,7 @@
37
37
  line. Raised from IDU_EMATE_APPL_WEB (#014). */
38
38
  .fdy-filterbar>.fdy-check,.fdy-filterbar>.fdy-radio,.fdy-filterbar>.fdy-switch,.fdy-filterbar>.fdy-btn{min-height:var(--control-h);}
39
39
  .fdy-filterbar__actions{display:flex;align-items:center;gap:var(--space-2);margin-left:auto;}
40
- /* Opt-in: sit the actions inline right after the last field instead of at the far edge — so when the
40
+ /* Opt-in: sit the actions inline right after the last field instead of at the far edge, so when the
41
41
  * bar wraps they read as attached to the filters rather than shoved to the right of a lonely row. */
42
42
  .fdy-filterbar--actions-inline .fdy-filterbar__actions{margin-left:0;}
43
43
  /* Stack on narrow screens so nothing overflows the row. */
@@ -1,8 +1,8 @@
1
- /* Freeday — Form grid (document/header fields laid out in aligned columns).
1
+ /* Freeday. Form grid (document/header fields laid out in aligned columns).
2
2
  * The two-dimensional counterpart to the single-column .fdy-field stack and the
3
3
  * one-row .fdy-filterbar: related fields sit in columns that reflow to one on narrow
4
4
  * screens. Fields are the standard .fdy-field; a field with --full spans the whole row
5
- * (e.g. an address or note). Zero JS — pure layout over .fdy-field. */
5
+ * (e.g. an address or note). Zero JS, pure layout over .fdy-field. */
6
6
  .fdy-form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(14rem,1fr));gap:var(--space-4) var(--space-5);}
7
7
  .fdy-form-grid>.fdy-field{max-width:none;}
8
8
  .fdy-form-grid>.fdy-field--full{grid-column:1/-1;}
@@ -1,4 +1,4 @@
1
- /* Freeday — Input group (prefix/suffix addons, e.g. Rp / % / icon) */
1
+ /* Freeday. Input group (prefix/suffix addons, e.g. Rp / % / icon) */
2
2
  .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);}
3
3
  .fdy-input-group:hover{border-color:var(--color-text-muted);}
4
4
  .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);}
@@ -11,23 +11,23 @@
11
11
  .fdy-input-group .fdy-input:focus{box-shadow:none;}
12
12
  .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);}
13
13
  .fdy-input-group__addon:last-child{border-right:0;border-left:var(--bw) solid var(--color-border);}
14
- /* Borderless leading icon (e.g. search magnifier) — self-contained like __btn so it
14
+ /* Borderless leading icon (e.g. search magnifier), self-contained like __btn so it
15
15
  centers standalone (no base .fdy-input-group__addon required). */
16
16
  .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);}
17
17
  .fdy-input-group__addon--icon svg{display:block;width:1rem;height:1rem;}
18
- /* The leading icon already provides the left inset — don't let the input add its full left padding on top. */
18
+ /* The leading icon already provides the left inset, don't let the input add its full left padding on top. */
19
19
  .fdy-input-group__addon--icon + .fdy-input{padding-left:var(--space-2);}
20
- /* Trailing action button (e.g. choose-from-list magnifier) — a real, focusable control. */
20
+ /* Trailing action button (e.g. choose-from-list magnifier), a real, focusable control. */
21
21
  .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);}
22
22
  .fdy-input-group__btn:hover{background:var(--color-surface-3);color:var(--color-text);}
23
23
  .fdy-input-group__btn:focus-visible{outline:none;color:var(--color-text);box-shadow:inset 0 0 0 2px var(--color-primary);}
24
24
  .fdy-input-group__btn svg{display:block;width:1.1rem;height:1.1rem;}
25
25
  /* Leading action button (the "−" of a number stepper): the divider belongs on the other side. */
26
26
  .fdy-input-group__btn:first-child{border-left:0;border-right:var(--bw) solid var(--color-border);}
27
- /* At a bound, or on a disabled/read-only field, the button must look as inert as it acts — same dim
27
+ /* At a bound, or on a disabled/read-only field, the button must look as inert as it acts, same dim
28
28
  + not-allowed as .fdy-input:disabled. :hover still matches a disabled button, so it is withdrawn. */
29
29
  .fdy-input-group__btn:disabled{opacity:.5;cursor:not-allowed;}
30
30
  .fdy-input-group__btn:disabled:hover{background:var(--color-surface-2);color:var(--color-text-muted);}
31
- /* Error state must win over the neutralizing rules above — promote to the wrapper. */
31
+ /* Error state must win over the neutralizing rules above, promote to the wrapper. */
32
32
  .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);}
33
33
  .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);}
@@ -1,10 +1,10 @@
1
- /* Freeday — Field & Input */
1
+ /* Freeday. Field & Input */
2
2
  .fdy-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
3
- /* A grouped field is legitimately a <fieldset> (it needs a <legend>) — e.g. a radio/checkbox group.
3
+ /* A grouped field is legitimately a <fieldset> (it needs a <legend>), e.g. a radio/checkbox group.
4
4
  Neutralise the UA groove border / padding / min-inline-size so it lays out identically to the
5
5
  <div> form (min-inline-size:min-content would otherwise block the filterbar column widths). */
6
6
  fieldset.fdy-field{border:0;margin:0;padding:0;min-inline-size:0;}
7
- /* The rendered legend is laid out outside the fieldset's flex flow, so `gap` never reaches it —
7
+ /* The rendered legend is laid out outside the fieldset's flex flow, so `gap` never reaches it,
8
8
  measured 0px between legend and first control. This is the spacing contract a grouped field
9
9
  needs; it matches .fdy-field's own gap so the group reads as one rhythm. */
10
10
  fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
@@ -22,17 +22,17 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
22
22
  .fdy-input[readonly]:hover,.fdy-textarea[readonly]:hover{border-color:var(--color-border);}
23
23
  .fdy-input[readonly]:focus-visible,.fdy-textarea[readonly]:focus-visible{border-color:var(--color-border-strong);box-shadow:none;}
24
24
  /* Numbers (#44): the user agent draws its own spin buttons inside the field, in OS widget colours no
25
- theme reaches — on a dark surface they read as a light-grey artefact glued to a themed field, and
25
+ theme reaches, on a dark surface they read as a light-grey artefact glued to a themed field, and
26
26
  it is the only unthemed control on the page. Removed so .fdy-input looks the same whatever its
27
27
  type. `appearance:textfield` is the Firefox half, the pseudo-element the Blink/WebKit half; both
28
- are needed. This trades away the mouse-only increment — arrow keys on a focused number input still
28
+ are needed. This trades away the mouse-only increment, arrow keys on a focused number input still
29
29
  work, and [data-fdy-number] gives the affordance back on the kit's terms. */
30
30
  .fdy-input[type="number"]{appearance:textfield;-webkit-appearance:textfield;}
31
31
  .fdy-input[type="number"]::-webkit-outer-spin-button,.fdy-input[type="number"]::-webkit-inner-spin-button{appearance:none;-webkit-appearance:none;margin:0;}
32
32
  .fdy-input--error,.fdy-input[aria-invalid="true"],.fdy-textarea[aria-invalid="true"]{border-color:var(--color-danger);}
33
33
  .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);}
34
34
  /* Required marker. The control already carries `required`, so the asterisk is decoration: the
35
- `/ ""` is CSS alt text — the glyph is painted but exposes NOTHING to the accessibility tree, so a
35
+ `/ ""` is CSS alt text, the glyph is painted but exposes NOTHING to the accessibility tree, so a
36
36
  screen reader does not read "star" after every label. That is stricter than a <span> an app has to
37
37
  remember to mark aria-hidden, and it cannot be forgotten. */
38
38
  .fdy-label--required::after{content:"*" / "";margin-left:var(--space-1);color:var(--color-danger);}
@@ -1,2 +1,2 @@
1
- /* Freeday — Keyboard key */
1
+ /* Freeday. Keyboard key */
2
2
  .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);}
@@ -1,17 +1,17 @@
1
- /* Freeday — List: the FLAT row container.
1
+ /* Freeday. List: the FLAT row container.
2
2
  *
3
3
  * USAGE.md §3 puts list rows among the surfaces that should be flat, but until now the only
4
- * container the kit shipped was .fdy-card — which carries --shadow-lift (a real 34px lift). A
4
+ * container the kit shipped was .fdy-card, which carries --shadow-lift (a real 34px lift). A
5
5
  * responsive table that becomes a list below `md` therefore had to choose between a stack of
6
6
  * shadowed cards (against the doctrine) or a hand-built box, which needs a colour and so escapes
7
7
  * the token system. This is that missing container: one flat surface, hairline dividers, no shadow.
8
8
  *
9
9
  * Not to be confused with .fdy-list-reset (base.css), which only strips UA bullets/indent.
10
10
  * Works on <ul>/<ol> (list-style is reset here) or on plain <div>s. */
11
- /* position:relative — containing block for out-of-flow row content (see base.css). */
11
+ /* position:relative, containing block for out-of-flow row content (see base.css). */
12
12
  .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;}
13
13
  /* A row that hosts a stretched target has to be the anchor itself (#008 §1). Without this the
14
- overlay resolves against .fdy-list — which IS positioned — so EVERY row's target covers the whole
14
+ overlay resolves against .fdy-list, which IS positioned, so EVERY row's target covers the whole
15
15
  list and the last one in the DOM wins: measured, a click on row one opens row two, under row one's
16
16
  name, which looks exactly like working software. Opt-in via :has(), so a row that doesn't use the
17
17
  pattern is untouched and nothing an app absolutely positions inside a plain row starts resolving
@@ -20,7 +20,7 @@
20
20
  .fdy-list__row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);min-width:0;}
21
21
  /* Divider between rows. Two shapes are supported and both are load-bearing: rows as direct children
22
22
  * of the list, and rows wrapped in <li> (the semantic shape, where the ADJACENT siblings are the
23
- * <li>s — a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
23
+ * <li>s, a bare `.fdy-list__row + .fdy-list__row` silently matches nothing there). */
24
24
  .fdy-list__row + .fdy-list__row,
25
25
  .fdy-list > * + * > .fdy-list__row{border-top:var(--bw) solid var(--color-border-muted);}
26
26
  /* A row that is itself the control: render it as <button>/<a> and the UA box is reset without
@@ -29,7 +29,7 @@
29
29
  .fdy-list__row--button:hover,.fdy-list__row--interactive:hover{background:var(--color-surface-2);}
30
30
  .fdy-list__row--button:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary);}
31
31
  .fdy-list__row--interactive{cursor:pointer;}
32
- /* Disabled row. The reset above adopts the UA button box, but not its disabled state — so a row
32
+ /* Disabled row. The reset above adopts the UA button box, but not its disabled state, so a row
33
33
  disabled mid-flight (a redirect in progress, say) kept lighting up and kept a pointer cursor:
34
34
  a control answering the pointer while refusing input. Same contract as every other disabled
35
35
  control in the kit (dim + not-allowed), hover withdrawn, both the native and the aria- form. */
@@ -1,4 +1,4 @@
1
- /* Freeday — Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
1
+ /* Freeday. Menu (action popup) + Split button. Enhanced by freeday-menu.js. */
2
2
  .fdy-menu-wrap{position:relative;display:inline-flex;}
3
3
  .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);}
4
4
  .fdy-menu[hidden]{display:none;}
@@ -6,7 +6,7 @@
6
6
  .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;}
7
7
  .fdy-menu__item:hover,.fdy-menu__item.is-highlighted{background:var(--color-surface-3);}
8
8
  /* Focus is not hover. The enhancer moves real DOM focus (freeday-menu.js `.focus()`), so this is
9
- the only thing a keyboard user has to go on — with the hover fill alone, arrowing through the
9
+ the only thing a keyboard user has to go on, with the hover fill alone, arrowing through the
10
10
  menu is invisible. Same ring as .fdy-nav__item, which got this right. Reported twice. */
11
11
  .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);}
12
12
  .fdy-menu__item:disabled{opacity:.5;cursor:not-allowed;background:transparent;}
@@ -15,7 +15,7 @@
15
15
  .fdy-menu__sep{height:0;margin:var(--space-1) 0;border:0;border-top:var(--bw) solid var(--color-border-muted);}
16
16
  @media (prefers-reduced-motion:no-preference){.fdy-menu:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
17
17
 
18
- /* Split button — main action + attached menu toggle (caret) */
18
+ /* Split button, main action + attached menu toggle (caret) */
19
19
  .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);}
20
20
  /* One control: the two halves must stay coplanar. Neutralize each segment's own
21
21
  lift + drop-shadow (they would misalign the seam / glow a single half); the
@@ -1,4 +1,4 @@
1
- /* Freeday — Modal (native <dialog>) */
1
+ /* Freeday. Modal (native <dialog>) */
2
2
  .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;}
3
3
  .fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
4
4
  .fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
@@ -1,4 +1,4 @@
1
- /* Freeday — Pagination */
1
+ /* Freeday. Pagination */
2
2
  .fdy-pagination__list{display:flex;align-items:center;gap:var(--space-1);list-style:none;margin:0;padding:0;}
3
3
  .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);}
4
4
  .fdy-pagination__link:hover{background:var(--color-surface-2);border-color:var(--color-border);}
@@ -1,4 +1,4 @@
1
- /* Freeday — Progress */
1
+ /* Freeday. Progress */
2
2
  .fdy-progress{height:.5rem;border-radius:var(--radius-full);background:var(--color-surface-3);overflow:hidden;}
3
3
  .fdy-progress__bar{height:100%;border-radius:inherit;background:var(--color-primary);transition:width var(--dur-base) var(--ease-standard);}
4
4
  .fdy-progress--indeterminate{position:relative;}
@@ -1,4 +1,4 @@
1
- /* Freeday — Rating (star rating). Interactive = native radio group (arrow keys, form-assoc);
1
+ /* Freeday. Rating (star rating). Interactive = native radio group (arrow keys, form-assoc);
2
2
  * filled vs empty differ by fill-vs-outline (not colour alone). Enhanced by freeday-rating.js. */
3
3
  .fdy-rating{display:inline-flex;gap:var(--space-1);}
4
4
  .fdy-rating__star{display:inline-flex;line-height:0;cursor:pointer;}
@@ -11,6 +11,6 @@
11
11
  .fdy-rating--disabled .fdy-rating__star{cursor:not-allowed;}
12
12
  .fdy-rating--disabled .fdy-rating__star svg{opacity:.5;}
13
13
  /* Invalid: tint the (outline) empty stars danger so a required-but-empty rating reads as an error,
14
- paired with a help/error message — never colour alone. */
14
+ paired with a help/error message, never colour alone. */
15
15
  .fdy-rating--error .fdy-rating__star svg{stroke:var(--color-danger);}
16
16
  .fdy-rating--sm .fdy-rating__star svg{width:1.1rem;height:1.1rem;}
@@ -1,10 +1,10 @@
1
- /* Freeday — Selection controls */
1
+ /* Freeday. Selection controls */
2
2
  /* `flex:none` on every box, track and standalone checkbox is load-bearing, not tidiness.
3
3
  `.fdy-check` is an inline-flex row of [box, label], and a flex item with a WIDTH is still
4
- shrinkable — so as soon as the label wrapped to a second line the box gave up horizontal space
4
+ shrinkable, so as soon as the label wrapped to a second line the box gave up horizontal space
5
5
  to it and rendered an 18px-tall rectangle 13px wide. Squashed, off-square, and different from
6
6
  every sibling whose label happened to be short: a form of five checkboxes showed three sizes.
7
- Nothing in the declarations looked wrong, because the width was never overridden — it was
7
+ Nothing in the declarations looked wrong, because the width was never overridden, it was
8
8
  negotiated away. */
9
9
  .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;}
10
10
  .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);}
@@ -22,7 +22,7 @@
22
22
  .fdy-switch input:checked{background:var(--color-primary);}
23
23
  .fdy-switch input:checked::before{transform:translateX(calc(var(--control-switch-w) - var(--control-switch-h)));}
24
24
 
25
- /* Standalone checkbox (e.g. table row/select-all) — supports :indeterminate */
25
+ /* Standalone checkbox (e.g. table row/select-all), supports :indeterminate */
26
26
  .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);}
27
27
  .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%);}
28
28
  .fdy-checkbox:checked,.fdy-checkbox:indeterminate{background:var(--color-primary);border-color:var(--color-primary);}
@@ -1,4 +1,4 @@
1
- /* Freeday — Skeleton */
1
+ /* Freeday. Skeleton */
2
2
  .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;}
3
3
  .fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
4
4
  .fdy-skeleton--title{height:1.2rem;width:40%;}
@@ -1,4 +1,4 @@
1
- /* Freeday — Slider (native <input type="range"> themed via accent-color: keeps native
1
+ /* Freeday. Slider (native <input type="range"> themed via accent-color: keeps native
2
2
  * keyboard + ARIA, filled track and thumb come free). Optional value output + scale.
3
3
  * freeday-slider.js just mirrors the value into an <output for="…">. */
4
4
  .fdy-slider{width:100%;max-width:22rem;height:1.5rem;accent-color:var(--color-primary);cursor:pointer;}
@@ -1,4 +1,4 @@
1
- /* Freeday — Spinner */
1
+ /* Freeday. Spinner */
2
2
  .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;}
3
3
  .fdy-spinner--sm{width:1rem;height:1rem;border-width:2px;}
4
4
  .fdy-spinner--lg{width:2.5rem;height:2.5rem;border-width:3px;}