@birdycom/omniaui 0.0.0-stage → 0.11.1

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 (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -3
  3. package/bin/lib/add.mjs +126 -0
  4. package/bin/lib/catalog.mjs +169 -0
  5. package/bin/lib/project.mjs +90 -0
  6. package/bin/omniaui.mjs +70 -0
  7. package/dist/code-block.css +163 -0
  8. package/dist/components-extra.css +916 -0
  9. package/dist/components.css +10171 -0
  10. package/dist/components.min.css +1 -0
  11. package/dist/data-table.d.ts +9 -0
  12. package/dist/data-table.js +505 -0
  13. package/dist/docs-ui.css +267 -0
  14. package/dist/oc-theme.css +69 -0
  15. package/dist/parts/.gitkeep +0 -0
  16. package/dist/parts/app-accordion-accent.css +60 -0
  17. package/dist/parts/app-alpha-tabs.css +68 -0
  18. package/dist/parts/app-busy-overlay.css +25 -0
  19. package/dist/parts/app-column-menu.css +85 -0
  20. package/dist/parts/app-confirm-dialog.css +70 -0
  21. package/dist/parts/app-cookie-consent.css +63 -0
  22. package/dist/parts/app-data-table.css +174 -0
  23. package/dist/parts/app-greeting.css +47 -0
  24. package/dist/parts/app-list-toolbar.css +81 -0
  25. package/dist/parts/app-page-header.css +54 -0
  26. package/dist/parts/app-severity-card.css +91 -0
  27. package/dist/parts/app-sidebar-dark.css +15 -0
  28. package/dist/parts/app-sortable-row.css +52 -0
  29. package/dist/parts/app-stat-card.css +68 -0
  30. package/dist/parts/app-table-actions.css +71 -0
  31. package/dist/parts/app-table-compact.css +54 -0
  32. package/dist/parts/app-top-progress.css +68 -0
  33. package/dist/parts/app-tree.css +104 -0
  34. package/dist/parts/app-upgrade-banner.css +90 -0
  35. package/dist/parts/app-version-banner.css +80 -0
  36. package/dist/parts/data-chart-line.css +167 -0
  37. package/dist/parts/forms-affix-input.css +75 -0
  38. package/dist/parts/forms-barcode-input.css +75 -0
  39. package/dist/parts/forms-chip-filter.css +91 -0
  40. package/dist/parts/forms-color-select.css +97 -0
  41. package/dist/parts/forms-decimal-input.css +51 -0
  42. package/dist/parts/forms-filter-counts.css +94 -0
  43. package/dist/parts/forms-language-select.css +89 -0
  44. package/dist/parts/forms-number-input.css +98 -0
  45. package/dist/parts/forms-password-requirements.css +55 -0
  46. package/dist/parts/forms-phone-input.css +85 -0
  47. package/dist/parts/forms-time-input.css +74 -0
  48. package/dist/parts/forms-toggle-field.css +69 -0
  49. package/dist/parts/media-art.css +21 -0
  50. package/dist/parts/media-attachment.css +160 -0
  51. package/dist/parts/media-browser-frame.css +73 -0
  52. package/dist/parts/media-card.css +83 -0
  53. package/dist/parts/media-chat-composer.css +90 -0
  54. package/dist/parts/media-doc-viewer.css +151 -0
  55. package/dist/parts/media-emoji-picker.css +88 -0
  56. package/dist/parts/media-entity-card.css +52 -0
  57. package/dist/parts/media-flip-card.css +78 -0
  58. package/dist/parts/media-image-zoom.css +68 -0
  59. package/dist/parts/media-lightbox.css +131 -0
  60. package/dist/parts/media-message-thread.css +136 -0
  61. package/dist/parts/media-message.css +160 -0
  62. package/dist/parts/media-row.css +78 -0
  63. package/dist/parts/media-stack-card.css +82 -0
  64. package/dist/parts/media-terminal-log.css +76 -0
  65. package/dist/parts/media-thumb.css +71 -0
  66. package/dist/parts/site-fact-list.css +25 -0
  67. package/dist/parts/site-link-arrow.css +34 -0
  68. package/dist/parts/site-marquee.css +34 -0
  69. package/dist/parts/site-next-teaser.css +75 -0
  70. package/dist/parts/site-price-card.css +91 -0
  71. package/dist/parts/site-product-mark.css +66 -0
  72. package/dist/parts/site-project-card.css +111 -0
  73. package/dist/parts/site-prose.css +124 -0
  74. package/dist/parts/site-stack-list.css +48 -0
  75. package/dist/parts/site-timeline.css +54 -0
  76. package/dist/showcase.css +404 -0
  77. package/manifest.json +1108 -0
  78. package/package.json +69 -6
  79. package/snippets/accordion-accent.html +24 -0
  80. package/snippets/accordion.html +11 -0
  81. package/snippets/affix-input.html +30 -0
  82. package/snippets/alert-dialog.html +22 -0
  83. package/snippets/alert.html +5 -0
  84. package/snippets/alpha-tabs.html +37 -0
  85. package/snippets/aspect-ratio.html +15 -0
  86. package/snippets/attachment.html +43 -0
  87. package/snippets/avatar.html +6 -0
  88. package/snippets/back-link.html +5 -0
  89. package/snippets/badge.html +5 -0
  90. package/snippets/barcode-input.html +18 -0
  91. package/snippets/breadcrumb.html +6 -0
  92. package/snippets/browser-frame.html +18 -0
  93. package/snippets/busy-overlay.html +31 -0
  94. package/snippets/button-group.html +21 -0
  95. package/snippets/button.html +8 -0
  96. package/snippets/calendar.html +43 -0
  97. package/snippets/card.html +17 -0
  98. package/snippets/carousel.html +21 -0
  99. package/snippets/chart-line.html +46 -0
  100. package/snippets/chart.html +27 -0
  101. package/snippets/chat-composer.html +21 -0
  102. package/snippets/checkbox.html +5 -0
  103. package/snippets/checklist.html +23 -0
  104. package/snippets/chip-filter.html +14 -0
  105. package/snippets/code-window.html +20 -0
  106. package/snippets/collapsible.html +19 -0
  107. package/snippets/color-field.html +13 -0
  108. package/snippets/color-select.html +18 -0
  109. package/snippets/column-menu.html +38 -0
  110. package/snippets/combobox.html +19 -0
  111. package/snippets/command.html +24 -0
  112. package/snippets/confirm-dialog.html +32 -0
  113. package/snippets/context-menu.html +25 -0
  114. package/snippets/cookie-consent.html +70 -0
  115. package/snippets/copy-field.html +19 -0
  116. package/snippets/counter.html +2 -0
  117. package/snippets/data-table.html +151 -0
  118. package/snippets/date-picker.html +32 -0
  119. package/snippets/decimal-input.html +20 -0
  120. package/snippets/definition-list.html +20 -0
  121. package/snippets/demo-panel.html +18 -0
  122. package/snippets/doc-viewer.html +58 -0
  123. package/snippets/drawer.html +30 -0
  124. package/snippets/dropdown-menu.html +27 -0
  125. package/snippets/dropdown.html +9 -0
  126. package/snippets/dropzone.html +13 -0
  127. package/snippets/emoji-picker.html +51 -0
  128. package/snippets/empty-state.html +17 -0
  129. package/snippets/entity-card.html +26 -0
  130. package/snippets/expandable-table.html +26 -0
  131. package/snippets/fact-list.html +14 -0
  132. package/snippets/filter-counts.html +30 -0
  133. package/snippets/flip-card.html +22 -0
  134. package/snippets/form-field.html +6 -0
  135. package/snippets/greeting.html +22 -0
  136. package/snippets/heading.html +14 -0
  137. package/snippets/hover-card.html +18 -0
  138. package/snippets/image-zoom.html +30 -0
  139. package/snippets/input-group.html +10 -0
  140. package/snippets/input.html +5 -0
  141. package/snippets/item.html +26 -0
  142. package/snippets/kbd.html +13 -0
  143. package/snippets/label.html +9 -0
  144. package/snippets/language-select.html +26 -0
  145. package/snippets/launcher-card.html +14 -0
  146. package/snippets/lightbox.html +50 -0
  147. package/snippets/link-arrow.html +10 -0
  148. package/snippets/list-toolbar.html +33 -0
  149. package/snippets/marquee.html +15 -0
  150. package/snippets/media-card.html +28 -0
  151. package/snippets/media-row.html +21 -0
  152. package/snippets/media-thumb.html +12 -0
  153. package/snippets/menubar.html +30 -0
  154. package/snippets/message-thread.html +123 -0
  155. package/snippets/message.html +51 -0
  156. package/snippets/modal.html +15 -0
  157. package/snippets/native-select.html +15 -0
  158. package/snippets/navmenu.html +23 -0
  159. package/snippets/next-teaser.html +15 -0
  160. package/snippets/number-input.html +27 -0
  161. package/snippets/otp-input.html +9 -0
  162. package/snippets/page-header.html +24 -0
  163. package/snippets/pagination.html +6 -0
  164. package/snippets/password-input.html +24 -0
  165. package/snippets/password-requirements.html +30 -0
  166. package/snippets/phone-input.html +22 -0
  167. package/snippets/popover.html +15 -0
  168. package/snippets/price-card.html +34 -0
  169. package/snippets/product-mark.html +13 -0
  170. package/snippets/progress.html +4 -0
  171. package/snippets/project-card.html +23 -0
  172. package/snippets/prose.html +54 -0
  173. package/snippets/radio.html +17 -0
  174. package/snippets/recovery-codes.html +20 -0
  175. package/snippets/resizable.html +16 -0
  176. package/snippets/scroll-area.html +20 -0
  177. package/snippets/search.html +8 -0
  178. package/snippets/segmented.html +11 -0
  179. package/snippets/select.html +7 -0
  180. package/snippets/separator.html +15 -0
  181. package/snippets/severity-card.html +35 -0
  182. package/snippets/sheet.html +32 -0
  183. package/snippets/sidebar-docs.html +24 -0
  184. package/snippets/sidebar.html +55 -0
  185. package/snippets/site-footer.html +6 -0
  186. package/snippets/site-header.html +26 -0
  187. package/snippets/skeleton.html +4 -0
  188. package/snippets/slider.html +17 -0
  189. package/snippets/sortable-row.html +31 -0
  190. package/snippets/spinner.html +14 -0
  191. package/snippets/stack-card.html +17 -0
  192. package/snippets/stack-list.html +16 -0
  193. package/snippets/stat-card.html +24 -0
  194. package/snippets/stepper.html +13 -0
  195. package/snippets/switch.html +6 -0
  196. package/snippets/table-actions.html +39 -0
  197. package/snippets/table-compact.html +24 -0
  198. package/snippets/table.html +29 -0
  199. package/snippets/tabs.html +9 -0
  200. package/snippets/tag-input.html +60 -0
  201. package/snippets/terminal-log.html +23 -0
  202. package/snippets/textarea.html +3 -0
  203. package/snippets/theme-switch.html +6 -0
  204. package/snippets/time-input.html +26 -0
  205. package/snippets/timeline.html +28 -0
  206. package/snippets/toast-stack.html +10 -0
  207. package/snippets/toast.html +36 -0
  208. package/snippets/toc.html +13 -0
  209. package/snippets/toggle-field.html +20 -0
  210. package/snippets/toggle.html +22 -0
  211. package/snippets/tooltip.html +4 -0
  212. package/snippets/top-progress.html +12 -0
  213. package/snippets/tree.html +38 -0
  214. package/snippets/upgrade-banner.html +28 -0
  215. package/snippets/user.html +15 -0
  216. package/snippets/version-banner.html +15 -0
@@ -0,0 +1,63 @@
1
+ /* File: packages/ui/src/parts/app-cookie-consent.css | Map: popover box (corner / bar) -> text -> preferences (details)
2
+ -> category rows -> actions -> entry animation -> small screens
3
+ Cookie consent: a non-modal popover="manual" in the top layer, so it never blocks the page. Categories are rows with
4
+ the switch component; "necessary" is locked on. Based on the cookie banner of the Verdavita site. */
5
+
6
+ .oc-cookie-consent {
7
+ position: fixed;
8
+ inset: auto 1rem 1rem auto;
9
+ inline-size: min(100vw - 2rem, 28rem);
10
+ max-block-size: calc(100dvh - 2rem);
11
+ margin: 0;
12
+ padding: 1.25rem;
13
+ overflow: auto;
14
+ border: 1px solid var(--oc-border);
15
+ border-radius: 12px;
16
+ background: var(--oc-popover, var(--oc-card));
17
+ color: var(--oc-foreground);
18
+ box-shadow: 0 1rem 2.5rem oklch(from var(--color-black-50) l c h / 0.2);
19
+ }
20
+ .oc-cookie-consent:popover-open { display: grid; gap: 0.9rem; }
21
+ /* Full-width bar at the bottom */
22
+ .oc-cookie-consent--bar { inset: auto 0 0 0; inline-size: 100%; max-inline-size: none; border-radius: 0; border-inline: 0; border-block-end: 0; }
23
+ .oc-cookie-consent--bar:popover-open { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
24
+ .oc-cookie-consent--bar .oc-cookie-consent__prefs { grid-column: 1 / -1; }
25
+
26
+ .oc-cookie-consent__title { display: flex; align-items: center; gap: 0.5rem; margin: 0; font-size: 1rem; font-weight: 700; }
27
+ .oc-cookie-consent__text { margin: 0; color: var(--oc-muted-foreground); font-size: 0.875rem; line-height: 1.55; }
28
+ .oc-cookie-consent__text a { color: var(--oc-link-color, var(--oc-primary)); }
29
+
30
+ /* Preferences: a native <details>, so "Customize" needs no script */
31
+ .oc-cookie-consent__prefs { border-block: 1px solid var(--oc-border); }
32
+ .oc-cookie-consent__prefs > summary {
33
+ padding-block: 0.6rem;
34
+ font-size: 0.875rem;
35
+ font-weight: 600;
36
+ cursor: pointer;
37
+ }
38
+ .oc-cookie-consent__prefs > summary:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 2px; border-radius: 4px; }
39
+ .oc-cookie-consent__category {
40
+ display: grid;
41
+ grid-template-columns: minmax(0, 1fr) auto;
42
+ align-items: center;
43
+ gap: 0.15rem 1rem;
44
+ padding-block: 0.6rem;
45
+ border-block-start: 1px solid var(--oc-border);
46
+ }
47
+ .oc-cookie-consent__category-name { font-size: 0.875rem; font-weight: 600; }
48
+ .oc-cookie-consent__category-text { grid-column: 1; color: var(--oc-muted-foreground); font-size: 0.8rem; }
49
+ .oc-cookie-consent__category > .oc-switch { grid-row: 1 / span 2; grid-column: 2; }
50
+
51
+ .oc-cookie-consent__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem; }
52
+ .oc-cookie-consent__actions > .oc-btn { flex: 1 1 auto; }
53
+
54
+ /* Entry: slide up from the corner */
55
+ .oc-cookie-consent { transition: opacity 0.3s, translate 0.3s, overlay 0.3s allow-discrete, display 0.3s allow-discrete; }
56
+ @starting-style { .oc-cookie-consent:popover-open { opacity: 0; translate: 0 1rem; } }
57
+ .oc-cookie-consent:not(:popover-open) { opacity: 0; translate: 0 1rem; }
58
+
59
+ @media (width < 36rem) {
60
+ .oc-cookie-consent { inset: auto 0.5rem 0.5rem; inline-size: auto; }
61
+ .oc-cookie-consent--bar:popover-open { grid-template-columns: 1fr; }
62
+ }
63
+ @media (prefers-reduced-motion: reduce) { .oc-cookie-consent { transition: none; } }
@@ -0,0 +1,174 @@
1
+ /* File: packages/ui/src/parts/app-data-table.css | Map: shell -> toolbar -> frame and scroll -> header resize -> select and actions columns -> deleted row -> bulk bar -> card view -> footer and pager -> compact density */
2
+ /* List table from Korrekt and OmniaDesk. The frame, toolbar and footer are styling.
3
+ Behaviour (search, sort, pages, resize, drag, trash, bulk) lives in src/data-table.js.
4
+ Set --oc-data-table-max on the shell when the body should scroll and the header should stick. */
5
+
6
+ .oc-data-table {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: 0.75rem;
10
+ padding: 1rem;
11
+ color: var(--oc-card-foreground);
12
+ background: var(--oc-card);
13
+ border: 1px solid var(--oc-border);
14
+ border-radius: var(--oc-radius);
15
+ }
16
+
17
+ /* Toolbar: title + gear, search, trash pills, table/cards toggle, page actions. */
18
+ .oc-data-table-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
19
+ .oc-data-table-heading { display: flex; align-items: center; gap: 0.5rem; min-inline-size: 0; }
20
+ .oc-data-table-title { font-size: 0.875rem; font-weight: 600; }
21
+ .oc-data-table-search { flex: 1 1 12rem; min-inline-size: 10rem; max-inline-size: 22rem; }
22
+ .oc-data-table-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-inline-start: auto; }
23
+ .oc-data-table-view { display: inline-flex; gap: 0.25rem; }
24
+ .oc-data-table-view [aria-pressed="true"] {
25
+ color: var(--oc-primary);
26
+ background: oklch(from var(--oc-primary) l c h / 0.1);
27
+ border-color: oklch(from var(--oc-primary) l c h / 0.35);
28
+ }
29
+ .oc-data-table-sort-summary { flex-basis: 100%; margin: 0; font-size: 0.75rem; color: var(--oc-muted-foreground); }
30
+ .oc-data-table-sort-summary:empty { display: none; }
31
+ .oc-data-table-file { display: none; }
32
+
33
+ /* The table sits in its own rounded frame. The scroll box keeps the header sticky. */
34
+ .oc-data-table-frame {
35
+ overflow: hidden;
36
+ background: var(--oc-background);
37
+ border: 1px solid var(--oc-border);
38
+ border-radius: calc(var(--oc-radius) - 2px);
39
+ }
40
+ .oc-data-table-scroll { overflow: auto; max-block-size: var(--oc-data-table-max, none); }
41
+ .oc-data-table .oc-table :is(th, td) { border-inline: 0; border-block-start: 0; }
42
+ .oc-data-table .oc-table thead th {
43
+ position: sticky;
44
+ inset-block-start: 0;
45
+ z-index: 1;
46
+ block-size: 2.5rem;
47
+ font-size: 0.75rem;
48
+ font-weight: 500;
49
+ background: var(--oc-card);
50
+ }
51
+ .oc-data-table .oc-table tbody tr:nth-child(even) { background: var(--oc-surface); }
52
+ .oc-data-table .oc-table tbody tr:last-child > td { border-block-end: 0; }
53
+
54
+ /* Header label and the drag handle that sets the column width. */
55
+ .oc-data-table-th { display: flex; align-items: center; gap: 0.25rem; position: relative; min-inline-size: 0; }
56
+ .oc-data-table-resize {
57
+ position: absolute;
58
+ inset-block: 0;
59
+ inset-inline-end: -0.45rem;
60
+ inline-size: 0.55rem;
61
+ padding: 0;
62
+ background: none;
63
+ border: 0;
64
+ cursor: col-resize;
65
+ touch-action: none;
66
+ }
67
+ .oc-data-table-resize::after {
68
+ content: "";
69
+ position: absolute;
70
+ inset-block: 0.4rem;
71
+ inset-inline-start: 0.25rem;
72
+ inline-size: 1px;
73
+ background: var(--oc-border);
74
+ }
75
+ .oc-data-table.is-resizing { cursor: col-resize; user-select: none; }
76
+ /* After a resize the table grows with its columns and the frame scrolls sideways. */
77
+ .oc-data-table-table--sized { inline-size: max-content; min-inline-size: 100%; }
78
+
79
+ /* Narrow control columns. The actions column stays on the right while the rest scrolls. */
80
+ .oc-data-table .oc-table :is(th, td)[data-oc-col="select"],
81
+ .oc-data-table .oc-table :is(th, td)[data-oc-col="order"] {
82
+ inline-size: 2.25rem;
83
+ padding-inline: 0.25rem;
84
+ text-align: center;
85
+ }
86
+ .oc-data-table .oc-table :is(th, td)[data-oc-col="actions"] {
87
+ position: sticky;
88
+ inset-inline-end: 0;
89
+ z-index: 2;
90
+ background: var(--oc-card);
91
+ box-shadow: -1px 0 0 var(--oc-border);
92
+ }
93
+ .oc-data-table .oc-table thead th[data-oc-col="actions"] { z-index: 3; }
94
+ .oc-data-table .oc-table tbody tr:nth-child(even) > td[data-oc-col="actions"] { background: var(--oc-surface); }
95
+
96
+ /* Trash and archive rows stay readable but quieter than active rows. */
97
+ .oc-data-table tr[data-oc-status="deleted"],
98
+ .oc-data-table tr[data-oc-status="archived"] { color: var(--oc-muted-foreground); }
99
+
100
+ /* Bar above the table once at least one row is checked. */
101
+ .oc-data-table-bulk {
102
+ display: flex;
103
+ flex-wrap: wrap;
104
+ align-items: center;
105
+ gap: 0.5rem;
106
+ padding: 0.5rem 0.75rem;
107
+ background: var(--oc-surface);
108
+ border: 1px solid var(--oc-border);
109
+ border-radius: var(--oc-radius);
110
+ }
111
+ .oc-data-table-bulk[hidden] { display: none; }
112
+
113
+ /* Card view: one card per row on this page. The script fills it. */
114
+ .oc-data-table-grid { display: none; }
115
+ .oc-data-table--grid .oc-data-table-scroll { display: none; }
116
+ .oc-data-table--grid .oc-data-table-grid {
117
+ display: grid;
118
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
119
+ gap: 0.75rem;
120
+ padding: 0.75rem;
121
+ }
122
+ .oc-data-table-card {
123
+ padding: 0.75rem;
124
+ background: var(--oc-card);
125
+ border: 1px solid var(--oc-border);
126
+ border-radius: calc(var(--oc-radius) - 2px);
127
+ }
128
+ .oc-data-table-card-title { margin: 0; font-size: 0.875rem; font-weight: 600; }
129
+
130
+ .oc-data-table-empty { margin: 0; padding: 2rem 1rem; text-align: center; color: var(--oc-muted-foreground); }
131
+ .oc-data-table-empty[hidden] { display: none; }
132
+
133
+ /* Footer: count on the left, rows-per-page in the middle, pager on the right. */
134
+ .oc-data-table-footer {
135
+ display: grid;
136
+ grid-template-columns: 1fr auto 1fr;
137
+ align-items: center;
138
+ gap: 0.75rem;
139
+ }
140
+ .oc-data-table-summary { margin: 0; font-size: 0.8125rem; color: var(--oc-muted-foreground); }
141
+ .oc-data-table-per-page { display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.8125rem; }
142
+ .oc-data-table-per-page .oc-select { inline-size: auto; }
143
+ .oc-data-table-pager { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.25rem; }
144
+ .oc-data-table-page {
145
+ min-inline-size: 2rem;
146
+ padding: 0.25rem 0.45rem;
147
+ font: inherit;
148
+ font-size: 0.8125rem;
149
+ color: var(--oc-foreground);
150
+ background: var(--oc-card);
151
+ border: 1px solid var(--oc-border);
152
+ border-radius: calc(var(--oc-radius) - 2px);
153
+ cursor: pointer;
154
+ }
155
+ .oc-data-table-page[aria-current="page"] {
156
+ color: var(--oc-primary);
157
+ background: oklch(from var(--oc-primary) l c h / 0.1);
158
+ border-color: oklch(from var(--oc-primary) l c h / 0.35);
159
+ }
160
+ .oc-data-table-page:disabled { opacity: 0.4; cursor: default; }
161
+ .oc-data-table-page:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
162
+ .oc-data-table-ellipsis { padding-inline: 0.25rem; color: var(--oc-muted-foreground); }
163
+
164
+ /* Design choice from the gear menu: tighter rows, like the compact article lists. */
165
+ .oc-data-table--compact .oc-table :is(th, td) { padding: 0.25rem 0.5rem; font-size: 0.8125rem; }
166
+ .oc-data-table--compact .oc-table thead th { block-size: 2rem; }
167
+
168
+ @media (max-width: 40rem) {
169
+ .oc-data-table-footer { grid-template-columns: 1fr; }
170
+ .oc-data-table-pager { justify-content: flex-start; }
171
+ }
172
+ @media (prefers-reduced-motion: reduce) {
173
+ .oc-data-table .oc-table tbody tr { transition: none; }
174
+ }
@@ -0,0 +1,47 @@
1
+ /* File: packages/ui/src/parts/app-greeting.css | Map: header row -> icon tile -> text (eyebrow, title, date) -> actions -> time-of-day modifiers -> size modifier */
2
+ /* Greeting: dashboard header with a time-of-day greeting ("Good morning, Ada") and today's date.
3
+ CSS cannot read the clock, so the app (or a wrapper) picks the modifier:
4
+ --morning (5-12h), --afternoon (12-18h), --evening (18-5h). The modifier only tints the icon tile. */
5
+
6
+ .oc-greeting {
7
+ --oc-greeting-color: var(--oc-primary);
8
+ display: flex;
9
+ flex-wrap: wrap;
10
+ align-items: center;
11
+ gap: 0.75rem 1rem;
12
+ padding-block: 0.5rem;
13
+ }
14
+
15
+ /* Icon tile (sun, sun-low, moon): tinted with the time-of-day color. */
16
+ .oc-greeting-icon {
17
+ display: grid;
18
+ place-items: center;
19
+ flex-shrink: 0;
20
+ inline-size: 2.75rem;
21
+ block-size: 2.75rem;
22
+ color: var(--oc-greeting-color);
23
+ background: oklch(from var(--oc-greeting-color) l c h / 0.12);
24
+ border-radius: calc(var(--oc-radius) + 4px);
25
+ }
26
+ .oc-greeting-icon svg { inline-size: 1.375rem; block-size: 1.375rem; }
27
+
28
+ /* Text column. */
29
+ .oc-greeting-body { flex: 1 1 16rem; min-inline-size: 0; }
30
+ .oc-greeting-eyebrow { margin: 0 0 0.125rem; font-size: 0.75rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--oc-greeting-color); }
31
+ .oc-greeting-title { margin: 0; font-size: 1.5rem; font-weight: 600; line-height: 1.2; letter-spacing: -0.01em; color: var(--oc-foreground); text-wrap: balance; }
32
+ .oc-greeting-name { color: var(--oc-greeting-color); }
33
+ /* Date: use <time datetime="2026-10-15">. First letter capitalised for locales that write weekdays in lower case. */
34
+ .oc-greeting-date { display: block; margin-block-start: 0.25rem; font-size: 0.875rem; color: var(--oc-muted-foreground); }
35
+ .oc-greeting-date::first-letter { text-transform: uppercase; }
36
+
37
+ /* Actions at the end (buttons, search). */
38
+ .oc-greeting-actions { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
39
+
40
+ /* Time-of-day tints. */
41
+ .oc-greeting--morning { --oc-greeting-color: var(--oc-warning); }
42
+ .oc-greeting--afternoon { --oc-greeting-color: var(--oc-primary); }
43
+ .oc-greeting--evening { --oc-greeting-color: light-dark(var(--color-purple-40), var(--color-purple-10)); }
44
+
45
+ /* Large: for a hero-like start page. */
46
+ .oc-greeting--lg .oc-greeting-title { font-size: clamp(1.5rem, 1.2rem + 1.5vw, 2.25rem); }
47
+ .oc-greeting--lg .oc-greeting-icon { inline-size: 3.5rem; block-size: 3.5rem; }
@@ -0,0 +1,81 @@
1
+ /* File: packages/ui/src/parts/app-list-toolbar.css | Map: toolbar row -> search -> count -> view toggle (radio icons) -> actions -> sticky modifier -> CSS-only view switch (scope) -> narrow screens */
2
+ /* List toolbar: search field + result count + cards / table view toggle + actions, above a list.
3
+ The view toggle is a radio group. Inside .oc-list-toolbar-scope the checked radio shows the matching
4
+ panel (.oc-list-toolbar-panel[data-view="cards" | "table"]) with :has(); no JavaScript. */
5
+
6
+ .oc-list-toolbar {
7
+ display: flex;
8
+ flex-wrap: wrap;
9
+ align-items: center;
10
+ gap: 0.5rem 0.75rem;
11
+ padding-block: 0.5rem;
12
+ }
13
+
14
+ /* Search: a .oc-input (or .oc-search) that takes the free space, up to 24rem. */
15
+ .oc-list-toolbar-search { flex: 1 1 14rem; max-inline-size: 24rem; }
16
+ .oc-list-toolbar-search .oc-input { inline-size: 100%; }
17
+
18
+ /* Result count ("128 items"). */
19
+ .oc-list-toolbar-count { font-size: 0.8125rem; color: var(--oc-muted-foreground); font-variant-numeric: tabular-nums; white-space: nowrap; }
20
+
21
+ /* Pushes everything after it to the end edge. */
22
+ .oc-list-toolbar-spacer { flex: 1; }
23
+
24
+ /* View toggle: radio inputs inside labels, shown as small icon buttons in one frame. */
25
+ .oc-list-toolbar-views {
26
+ display: inline-flex;
27
+ padding: 2px;
28
+ background: var(--oc-muted);
29
+ border: 1px solid var(--oc-border);
30
+ border-radius: var(--oc-radius);
31
+ }
32
+ .oc-list-toolbar-view {
33
+ position: relative;
34
+ display: inline-grid;
35
+ place-items: center;
36
+ inline-size: 2rem;
37
+ block-size: 1.75rem;
38
+ color: var(--oc-muted-foreground);
39
+ border-radius: calc(var(--oc-radius) - 2px);
40
+ cursor: pointer;
41
+ transition: color var(--oc-transition-fast), background var(--oc-transition-fast);
42
+ }
43
+ /* The radio stays in the accessibility tree but is invisible; the label is the click target. */
44
+ .oc-list-toolbar-view input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: inherit; }
45
+ .oc-list-toolbar-view svg { inline-size: 1rem; block-size: 1rem; }
46
+ .oc-list-toolbar-view:hover { color: var(--oc-foreground); }
47
+ .oc-list-toolbar-view:has(input:checked) {
48
+ color: var(--oc-foreground);
49
+ background: var(--oc-background);
50
+ box-shadow: 0 1px 3px oklch(from var(--color-black-60) l c h / 0.1);
51
+ }
52
+ .oc-list-toolbar-view:has(input:focus-visible) { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
53
+
54
+ /* Buttons at the end (New, Export, filter dropdown ...). */
55
+ .oc-list-toolbar-actions { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
56
+
57
+ /* Sticky: stays at the top of the scroll container while the list scrolls. */
58
+ .oc-list-toolbar--sticky {
59
+ position: sticky;
60
+ inset-block-start: 0;
61
+ z-index: 5;
62
+ background: var(--oc-background);
63
+ border-block-end: 1px solid var(--oc-border);
64
+ }
65
+
66
+ /* Bordered: framed bar (for use above a card grid). */
67
+ .oc-list-toolbar--bordered { padding: 0.5rem 0.75rem; background: var(--oc-card); border: 1px solid var(--oc-border); border-radius: var(--oc-radius); }
68
+
69
+ /* ---- CSS-only view switch ---- */
70
+ .oc-list-toolbar-scope:has(.oc-list-toolbar-view input[value="cards"]:checked) .oc-list-toolbar-panel:not([data-view="cards"]),
71
+ .oc-list-toolbar-scope:has(.oc-list-toolbar-view input[value="table"]:checked) .oc-list-toolbar-panel:not([data-view="table"]),
72
+ .oc-list-toolbar-scope:has(.oc-list-toolbar-view input[value="list"]:checked) .oc-list-toolbar-panel:not([data-view="list"]) { display: none; }
73
+
74
+ /* Narrow screens: search takes the full first row. */
75
+ @media (max-width: 36rem) {
76
+ .oc-list-toolbar-search { flex-basis: 100%; max-inline-size: none; }
77
+ }
78
+
79
+ @media (prefers-reduced-motion: reduce) {
80
+ .oc-list-toolbar-view { transition: none; }
81
+ }
@@ -0,0 +1,54 @@
1
+ /* File: packages/ui/src/parts/app-page-header.css | Map: header (container) -> back link row -> main row (text + actions) -> title + subtitle + meta -> wide layout (@container) -> modifiers (bordered, sticky, sm) */
2
+ /* Page header: back link + title + subtitle + actions at the top of an app page.
3
+ A lighter variant of the module-header block, as a component. The header is a size container:
4
+ below 36rem the actions wrap under the title, from 36rem they sit at the end of the title row. */
5
+
6
+ .oc-page-header {
7
+ container-type: inline-size;
8
+ container-name: oc-page-header;
9
+ display: grid;
10
+ gap: 0.5rem;
11
+ padding-block: 0.75rem 1rem;
12
+ }
13
+
14
+ /* Optional back link row (use .oc-back-link inside, or any link). */
15
+ .oc-page-header-back { display: flex; }
16
+ .oc-page-header-back .oc-back-link { margin-inline-start: -0.75rem; }
17
+
18
+ /* Main row: text block + actions. Stacks by default. */
19
+ .oc-page-header-main { display: flex; flex-direction: column; gap: 0.75rem; }
20
+ .oc-page-header-text { min-inline-size: 0; }
21
+ .oc-page-header-title {
22
+ display: flex;
23
+ flex-wrap: wrap;
24
+ align-items: center;
25
+ gap: 0.5rem;
26
+ margin: 0;
27
+ font-size: 1.5rem;
28
+ font-weight: 600;
29
+ line-height: 1.25;
30
+ letter-spacing: -0.01em;
31
+ color: var(--oc-foreground);
32
+ text-wrap: balance;
33
+ }
34
+ .oc-page-header-subtitle { margin: 0.25rem 0 0; max-inline-size: 65ch; font-size: 0.875rem; line-height: 1.55; color: var(--oc-muted-foreground); }
35
+ /* Meta row: badges, last-updated text. */
36
+ .oc-page-header-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.375rem 0.75rem; margin-block-start: 0.5rem; font-size: 0.8125rem; color: var(--oc-muted-foreground); }
37
+
38
+ .oc-page-header-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
39
+
40
+ /* Wide enough: actions at the end of the title row. */
41
+ @container oc-page-header (min-width: 36rem) {
42
+ .oc-page-header-main { flex-direction: row; align-items: flex-start; justify-content: space-between; }
43
+ .oc-page-header-actions { flex-shrink: 0; justify-content: flex-end; padding-block-start: 0.125rem; }
44
+ }
45
+
46
+ /* Bordered: line under the header. */
47
+ .oc-page-header--bordered { margin-block-end: 1rem; border-block-end: 1px solid var(--oc-border); }
48
+
49
+ /* Sticky: stays on top while the page scrolls (needs a background). */
50
+ .oc-page-header--sticky { position: sticky; inset-block-start: 0; z-index: 10; background: var(--oc-background); }
51
+
52
+ /* Small: for sub pages and dialogs. */
53
+ .oc-page-header--sm { padding-block: 0.5rem 0.75rem; }
54
+ .oc-page-header--sm .oc-page-header-title { font-size: 1.125rem; }
@@ -0,0 +1,91 @@
1
+ /* File: packages/ui/src/parts/app-severity-card.css | Map: card (details) -> head (summary) -> icon + title + tags -> level badge + chevron -> body + actions -> severity modifiers -> resolved state -> reduced motion */
2
+ /* Severity card: risk-colored expandable card (security finding, incident, audit item).
3
+ Built on <details> / <summary>, so it opens without JavaScript; same name="..." makes a group exclusive.
4
+ Levels: --low, --medium, --high, --critical. Resolved: .is-resolved (muted, no color). */
5
+
6
+ .oc-severity-card {
7
+ --oc-severity-color: var(--oc-muted-foreground);
8
+ overflow: hidden;
9
+ color: var(--oc-foreground);
10
+ background: color-mix(in oklch, var(--oc-severity-color) 6%, var(--oc-card));
11
+ border: 1px solid oklch(from var(--oc-severity-color) l c h / 0.3);
12
+ border-inline-start-width: 3px;
13
+ border-inline-start-color: var(--oc-severity-color);
14
+ border-radius: calc(var(--oc-radius) + 4px);
15
+ interpolate-size: allow-keywords;
16
+ }
17
+ .oc-severity-card + .oc-severity-card { margin-block-start: 0.5rem; }
18
+
19
+ /* Head row (summary): icon, title + tags, level badge, chevron. */
20
+ .oc-severity-card-head {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 0.75rem;
24
+ padding: 0.875rem 1rem;
25
+ cursor: pointer;
26
+ list-style: none;
27
+ user-select: none;
28
+ }
29
+ .oc-severity-card-head::-webkit-details-marker { display: none; }
30
+ .oc-severity-card-head:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: -2px; }
31
+ .oc-severity-card-head:hover { background: oklch(from var(--oc-severity-color) l c h / 0.06); }
32
+
33
+ .oc-severity-card-icon { display: inline-flex; flex-shrink: 0; color: var(--oc-severity-color); }
34
+ .oc-severity-card-icon svg { inline-size: 1.25rem; block-size: 1.25rem; }
35
+ .oc-severity-card-main { flex: 1; min-inline-size: 0; }
36
+ .oc-severity-card-title { display: block; overflow: hidden; font-size: 0.875rem; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; color: var(--oc-severity-color); }
37
+ /* Tags under the title (data types, categories). */
38
+ .oc-severity-card-tags { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-block-start: 0.375rem; }
39
+ .oc-severity-card-tag { padding: 0.125rem 0.375rem; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.6875rem; color: var(--oc-muted-foreground); background: var(--oc-muted); border-radius: 4px; }
40
+
41
+ /* Level badge ("High") in the severity color. */
42
+ .oc-severity-card-level {
43
+ flex-shrink: 0;
44
+ padding: 0.2rem 0.5rem;
45
+ font-size: 0.6875rem;
46
+ font-weight: 700;
47
+ letter-spacing: 0.04em;
48
+ text-transform: uppercase;
49
+ color: var(--oc-severity-color);
50
+ background: oklch(from var(--oc-severity-color) l c h / 0.14);
51
+ border: 1px solid oklch(from var(--oc-severity-color) l c h / 0.35);
52
+ border-radius: 999px;
53
+ }
54
+
55
+ /* Chevron (CSS borders), rotates when open. */
56
+ .oc-severity-card-head::after {
57
+ content: "";
58
+ flex-shrink: 0;
59
+ inline-size: 0.45rem;
60
+ block-size: 0.45rem;
61
+ margin-inline: 0.25rem;
62
+ border-inline-end: 1.5px solid var(--oc-muted-foreground);
63
+ border-block-end: 1.5px solid var(--oc-muted-foreground);
64
+ rotate: 45deg;
65
+ translate: 0 -0.15rem;
66
+ transition: rotate var(--oc-transition-fast);
67
+ }
68
+ .oc-severity-card[open] > .oc-severity-card-head::after { rotate: 225deg; translate: 0 0.1rem; }
69
+
70
+ /* Body: details text and an action row. Height animates where ::details-content is supported. */
71
+ .oc-severity-card::details-content { block-size: 0; overflow: clip; transition: block-size var(--oc-transition-normal), content-visibility var(--oc-transition-normal) allow-discrete; }
72
+ .oc-severity-card[open]::details-content { block-size: auto; }
73
+ .oc-severity-card-body { padding: 0 1rem 1rem 3rem; font-size: 0.875rem; line-height: 1.6; color: var(--oc-muted-foreground); }
74
+ .oc-severity-card-body > :first-child { margin-block-start: 0; }
75
+ .oc-severity-card-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-block-start: 0.75rem; }
76
+
77
+ /* Levels (theme tokens; critical is a deeper red). */
78
+ .oc-severity-card--low { --oc-severity-color: var(--oc-success); }
79
+ .oc-severity-card--medium { --oc-severity-color: var(--oc-warning); }
80
+ .oc-severity-card--high { --oc-severity-color: var(--oc-destructive); }
81
+ .oc-severity-card--critical { --oc-severity-color: light-dark(var(--color-red-95), var(--color-red-30)); }
82
+ .oc-severity-card--critical .oc-severity-card-level { color: var(--oc-destructive-foreground); background: var(--oc-severity-color); }
83
+
84
+ /* Resolved: back to neutral colors, title struck through. */
85
+ .oc-severity-card.is-resolved { --oc-severity-color: var(--oc-muted-foreground); background: var(--oc-surface); }
86
+ .oc-severity-card.is-resolved .oc-severity-card-title { text-decoration: line-through; }
87
+
88
+ @media (prefers-reduced-motion: reduce) {
89
+ .oc-severity-card::details-content,
90
+ .oc-severity-card-head::after { transition: none; }
91
+ }
@@ -0,0 +1,15 @@
1
+ /* File: packages/ui/src/parts/app-sidebar-dark.css | Map: dark sidebar -> active item accent
2
+ .oc-sidebar--dark: a sidebar that stays dark in the light theme too (classic CMS admin look).
3
+ All --oc-* tokens are light-dark() pairs, so color-scheme: dark on the sidebar switches every token inside it.
4
+ Based on the Verdavita admin sidebar. */
5
+
6
+ .oc-sidebar--dark {
7
+ color-scheme: dark;
8
+ background: var(--oc-card);
9
+ color: var(--oc-foreground);
10
+ border-color: var(--oc-border);
11
+ }
12
+ .oc-sidebar--dark .oc-sidebar-menu-button[aria-current="page"],
13
+ .oc-sidebar--dark .oc-sidebar-menu-button.is-active {
14
+ box-shadow: inset 3px 0 0 var(--oc-primary);
15
+ }
@@ -0,0 +1,52 @@
1
+ /* File: packages/ui/src/parts/app-sortable-row.css | Map: grip cell -> grip button (dots) -> row states (dragging, drop before / after) -> reduced motion */
2
+ /* Sortable row: a table row with a drag grip. This file is styling only.
3
+ Use native HTML drag and drop (draggable="true" on the row) and set the state classes from your own code:
4
+ .is-dragging on the row being moved, .is-drop-before / .is-drop-after on the row under the pointer. */
5
+
6
+ /* Narrow first column that holds the grip. */
7
+ .oc-sortable-row-grip-cell { inline-size: 2.25rem; padding-inline: 0.25rem; text-align: center; }
8
+
9
+ /* The grip itself: a button so it can take keyboard focus (move with your own key handler). */
10
+ .oc-sortable-row-grip {
11
+ display: inline-grid;
12
+ place-items: center;
13
+ inline-size: 1.5rem;
14
+ block-size: 1.75rem;
15
+ padding: 0;
16
+ color: var(--oc-muted-foreground);
17
+ background: none;
18
+ border: 0;
19
+ border-radius: calc(var(--oc-radius) - 2px);
20
+ cursor: grab;
21
+ touch-action: none;
22
+ }
23
+ /* Six dots (2 columns x 3 rows) drawn with a repeating radial gradient in currentColor. */
24
+ .oc-sortable-row-grip::before {
25
+ content: "";
26
+ inline-size: 0.5rem;
27
+ block-size: 0.875rem;
28
+ background: radial-gradient(circle, currentColor 1.1px, transparent 1.4px) 0 0 / 0.25rem 0.29rem;
29
+ }
30
+ .oc-sortable-row-grip:hover { color: var(--oc-foreground); background: var(--oc-accent); }
31
+ .oc-sortable-row-grip:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 1px; }
32
+ .oc-sortable-row-grip:active { cursor: grabbing; }
33
+
34
+ /* Row being dragged: dimmed and dashed so the original place stays visible. */
35
+ .oc-sortable-row { transition: opacity var(--oc-transition-fast), background var(--oc-transition-fast); }
36
+ .oc-sortable-row.is-dragging { opacity: 0.45; background: var(--oc-surface); }
37
+ .oc-sortable-row.is-dragging > td { border-style: dashed; }
38
+
39
+ /* Drop indicator: a primary line on the top or bottom edge of the target row. */
40
+ .oc-sortable-row.is-drop-before > td { box-shadow: inset 0 2px 0 var(--oc-primary); }
41
+ .oc-sortable-row.is-drop-after > td { box-shadow: inset 0 -2px 0 var(--oc-primary); }
42
+
43
+ /* Picked up with the keyboard (aria-pressed on the grip): highlight the whole row. */
44
+ .oc-sortable-row:has(.oc-sortable-row-grip[aria-pressed="true"]) { background: oklch(from var(--oc-primary) l c h / 0.08); }
45
+ .oc-sortable-row-grip[aria-pressed="true"] { color: var(--oc-primary); cursor: grabbing; }
46
+
47
+ /* Locked row (cannot move): no grab cursor, faded grip. */
48
+ .oc-sortable-row-grip:disabled { opacity: 0.35; cursor: not-allowed; }
49
+
50
+ @media (prefers-reduced-motion: reduce) {
51
+ .oc-sortable-row { transition: none; }
52
+ }
@@ -0,0 +1,68 @@
1
+ /* File: packages/ui/src/parts/app-stat-card.css | Map: grid -> card -> icon tile -> value + label -> trend -> tone modifiers -> clickable card -> reduced motion */
2
+ /* Stat card: compact KPI tile with an icon tile, a big number and a small label.
3
+ Tone modifiers recolor the icon tile: --accent (needs attention, red), --success, --warning.
4
+ Use an <a> or <button> as the card to make it clickable (hover lift). */
5
+
6
+ /* Responsive grid for a row of stat cards. */
7
+ .oc-stat-card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); gap: 0.75rem; }
8
+
9
+ .oc-stat-card {
10
+ --oc-stat-card-color: var(--oc-primary);
11
+ display: flex;
12
+ align-items: center;
13
+ gap: 0.75rem;
14
+ min-inline-size: 0;
15
+ padding: 1rem;
16
+ font: inherit;
17
+ text-align: start;
18
+ text-decoration: none;
19
+ color: var(--oc-card-foreground);
20
+ background: var(--oc-card-background);
21
+ border: 1px solid var(--oc-card-border);
22
+ border-radius: calc(var(--oc-radius) + 4px);
23
+ transition: translate var(--oc-transition-fast), box-shadow var(--oc-transition-fast), border-color var(--oc-transition-fast);
24
+ }
25
+
26
+ /* Icon tile, tinted with the tone color. */
27
+ .oc-stat-card-icon {
28
+ display: grid;
29
+ place-items: center;
30
+ flex-shrink: 0;
31
+ inline-size: 2.5rem;
32
+ block-size: 2.5rem;
33
+ color: var(--oc-stat-card-color);
34
+ background: oklch(from var(--oc-stat-card-color) l c h / 0.12);
35
+ border-radius: var(--oc-radius);
36
+ }
37
+ .oc-stat-card-icon svg { inline-size: 1.25rem; block-size: 1.25rem; }
38
+
39
+ /* Text column: number on top, label below (ellipsis when too long). */
40
+ .oc-stat-card-body { min-inline-size: 0; }
41
+ .oc-stat-card-value { display: block; font-size: 1.5rem; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; color: var(--oc-foreground); }
42
+ .oc-stat-card-label { display: block; margin-block-start: 0.25rem; overflow: hidden; font-size: 0.75rem; color: var(--oc-muted-foreground); text-overflow: ellipsis; white-space: nowrap; }
43
+
44
+ /* Optional trend chip next to the value ("+12%"). */
45
+ .oc-stat-card-trend { display: inline-block; margin-inline-start: 0.375rem; padding: 0.125rem 0.375rem; font-size: 0.6875rem; font-weight: 600; vertical-align: middle; border-radius: 999px; color: var(--oc-muted-foreground); background: var(--oc-muted); }
46
+ .oc-stat-card-trend--up { color: var(--oc-success); background: oklch(from var(--oc-success) l c h / 0.12); }
47
+ .oc-stat-card-trend--down { color: var(--oc-destructive); background: oklch(from var(--oc-destructive) l c h / 0.12); }
48
+
49
+ /* Tones. --accent = "needs attention" (e.g. open orders). */
50
+ .oc-stat-card--accent { --oc-stat-card-color: var(--oc-destructive); }
51
+ .oc-stat-card--success { --oc-stat-card-color: var(--oc-success); }
52
+ .oc-stat-card--warning { --oc-stat-card-color: var(--oc-warning); }
53
+ /* Accent stripe on the start edge (optional, combine with a tone). */
54
+ .oc-stat-card--stripe { border-inline-start: 3px solid var(--oc-stat-card-color); }
55
+
56
+ /* Clickable card (a / button): lift on hover, ring on keyboard focus. */
57
+ a.oc-stat-card,
58
+ button.oc-stat-card { cursor: pointer; }
59
+ a.oc-stat-card:hover,
60
+ button.oc-stat-card:hover { translate: 0 -2px; border-color: oklch(from var(--oc-stat-card-color) l c h / 0.4); box-shadow: 0 6px 18px oklch(from var(--color-black-60) l c h / 0.1); }
61
+ a.oc-stat-card:focus-visible,
62
+ button.oc-stat-card:focus-visible { outline: 2px solid var(--oc-ring); outline-offset: 2px; }
63
+
64
+ @media (prefers-reduced-motion: reduce) {
65
+ .oc-stat-card { transition: none; }
66
+ a.oc-stat-card:hover,
67
+ button.oc-stat-card:hover { translate: none; }
68
+ }