@valentinkolb/cloud 0.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 (196) hide show
  1. package/package.json +69 -0
  2. package/public/logo.svg +1 -0
  3. package/scripts/build.ts +113 -0
  4. package/scripts/preload.ts +73 -0
  5. package/src/_internal/define-app.ts +399 -0
  6. package/src/_internal/heartbeat.ts +33 -0
  7. package/src/_internal/registry.ts +100 -0
  8. package/src/_internal/runtime-context.ts +38 -0
  9. package/src/api/accounts-entities.ts +134 -0
  10. package/src/api/admin-lifecycle.ts +210 -0
  11. package/src/api/auth/schemas.ts +28 -0
  12. package/src/api/auth.ts +230 -0
  13. package/src/api/index.ts +66 -0
  14. package/src/api/me.ts +206 -0
  15. package/src/api/search/schemas.ts +43 -0
  16. package/src/api/search.ts +130 -0
  17. package/src/clients/core.ts +19 -0
  18. package/src/config/env.ts +23 -0
  19. package/src/config/index.ts +6 -0
  20. package/src/config/ssr.ts +58 -0
  21. package/src/contracts/app.ts +140 -0
  22. package/src/contracts/index.ts +5 -0
  23. package/src/contracts/profile.ts +67 -0
  24. package/src/contracts/registry.ts +50 -0
  25. package/src/contracts/settings-types.ts +84 -0
  26. package/src/contracts/shared.ts +258 -0
  27. package/src/contracts/widgets.ts +121 -0
  28. package/src/index.ts +6 -0
  29. package/src/server/api/index.ts +1 -0
  30. package/src/server/api/respond.ts +55 -0
  31. package/src/server/api-client.ts +54 -0
  32. package/src/server/app-context.ts +39 -0
  33. package/src/server/index.ts +62 -0
  34. package/src/server/middleware/auth.ts +168 -0
  35. package/src/server/middleware/index.ts +7 -0
  36. package/src/server/middleware/middleware.ts +47 -0
  37. package/src/server/middleware/openapi.ts +126 -0
  38. package/src/server/middleware/rate-limit.ts +126 -0
  39. package/src/server/middleware/request-logger.ts +41 -0
  40. package/src/server/middleware/validator.ts +35 -0
  41. package/src/server/services/access.ts +294 -0
  42. package/src/server/services/freeipa/client.ts +100 -0
  43. package/src/server/services/freeipa/index.ts +9 -0
  44. package/src/server/services/freeipa/session.ts +78 -0
  45. package/src/server/services/freeipa/tls.ts +48 -0
  46. package/src/server/services/freeipa/util.ts +60 -0
  47. package/src/server/services/geo.ts +154 -0
  48. package/src/server/services/index.ts +28 -0
  49. package/src/server/services/services.ts +13 -0
  50. package/src/services/account-lifecycle/audit.ts +41 -0
  51. package/src/services/account-lifecycle/index.ts +907 -0
  52. package/src/services/account-lifecycle/scheduler.ts +347 -0
  53. package/src/services/account-model.ts +21 -0
  54. package/src/services/accounts/app.ts +966 -0
  55. package/src/services/accounts/authz.ts +22 -0
  56. package/src/services/accounts/base-group.ts +11 -0
  57. package/src/services/accounts/base-user.ts +45 -0
  58. package/src/services/accounts/entities.ts +529 -0
  59. package/src/services/accounts/group-sql.ts +106 -0
  60. package/src/services/accounts/groups.ts +246 -0
  61. package/src/services/accounts/index.ts +14 -0
  62. package/src/services/accounts/ipa-data.ts +64 -0
  63. package/src/services/accounts/lifecycle.ts +2 -0
  64. package/src/services/accounts/local-groups.ts +491 -0
  65. package/src/services/accounts/model.ts +135 -0
  66. package/src/services/accounts/switching.ts +117 -0
  67. package/src/services/accounts/users.ts +714 -0
  68. package/src/services/auth-flows/index.ts +6 -0
  69. package/src/services/auth-flows/ipa.ts +128 -0
  70. package/src/services/auth-flows/magic-link.ts +119 -0
  71. package/src/services/freeipa-config.ts +89 -0
  72. package/src/services/index.ts +46 -0
  73. package/src/services/ipa/auth.ts +122 -0
  74. package/src/services/ipa/groups.ts +684 -0
  75. package/src/services/ipa/guard.ts +17 -0
  76. package/src/services/ipa/index.ts +17 -0
  77. package/src/services/ipa/profile.ts +90 -0
  78. package/src/services/ipa/search.ts +154 -0
  79. package/src/services/ipa/sync.ts +740 -0
  80. package/src/services/ipa/users.ts +794 -0
  81. package/src/services/logging/index.ts +294 -0
  82. package/src/services/notifications/email.ts +123 -0
  83. package/src/services/notifications/index.ts +413 -0
  84. package/src/services/postgres.ts +51 -0
  85. package/src/services/providers/index.ts +27 -0
  86. package/src/services/providers/local/auth.ts +13 -0
  87. package/src/services/providers/local/index.ts +4 -0
  88. package/src/services/providers/local/users.ts +255 -0
  89. package/src/services/session/index.ts +137 -0
  90. package/src/services/settings/api.ts +61 -0
  91. package/src/services/settings/app.ts +101 -0
  92. package/src/services/settings/crypto.ts +69 -0
  93. package/src/services/settings/defaults.ts +824 -0
  94. package/src/services/settings/index.ts +203 -0
  95. package/src/services/settings/namespace.ts +9 -0
  96. package/src/services/settings/snapshot.ts +49 -0
  97. package/src/services/settings/store.ts +179 -0
  98. package/src/services/settings/templates.ts +10 -0
  99. package/src/services/weather/forecast.ts +287 -0
  100. package/src/services/weather/geo.ts +110 -0
  101. package/src/services/weather/index.ts +99 -0
  102. package/src/services/weather/location.ts +24 -0
  103. package/src/services/weather/locations.ts +125 -0
  104. package/src/services/weather/migrate.ts +22 -0
  105. package/src/services/weather/types.ts +61 -0
  106. package/src/services/weather/ui.ts +50 -0
  107. package/src/shared/account-display.ts +17 -0
  108. package/src/shared/account-session.ts +15 -0
  109. package/src/shared/icons.ts +109 -0
  110. package/src/shared/index.ts +10 -0
  111. package/src/shared/markdown/client.ts +130 -0
  112. package/src/shared/markdown/extensions/code.ts +58 -0
  113. package/src/shared/markdown/extensions/images.ts +43 -0
  114. package/src/shared/markdown/extensions/info-blocks.ts +93 -0
  115. package/src/shared/markdown/extensions/katex.ts +120 -0
  116. package/src/shared/markdown/extensions/links.ts +34 -0
  117. package/src/shared/markdown/extensions/tables.ts +88 -0
  118. package/src/shared/markdown/extensions/task-list.ts +53 -0
  119. package/src/shared/markdown/index.ts +97 -0
  120. package/src/shared/markdown/shared.ts +36 -0
  121. package/src/ssr/AdminLayout.tsx +42 -0
  122. package/src/ssr/AdminSidebar.tsx +95 -0
  123. package/src/ssr/Footer.island.tsx +62 -0
  124. package/src/ssr/GlobalSearchDialog.tsx +389 -0
  125. package/src/ssr/GlobalSearchHelpDialog.tsx +106 -0
  126. package/src/ssr/GlobalSearchTrigger.island.tsx +42 -0
  127. package/src/ssr/HotkeysHelpRail.island.tsx +99 -0
  128. package/src/ssr/Layout.tsx +326 -0
  129. package/src/ssr/MoreAppsDropdown.island.tsx +61 -0
  130. package/src/ssr/NavMenu.island.tsx +108 -0
  131. package/src/ssr/ThemeToggleRail.island.tsx +27 -0
  132. package/src/ssr/index.ts +5 -0
  133. package/src/ssr/islands/SearchBar.island.tsx +77 -0
  134. package/src/ssr/islands/index.ts +1 -0
  135. package/src/ssr/runtime.ts +22 -0
  136. package/src/styles/base-popover.css +28 -0
  137. package/src/styles/effects.css +65 -0
  138. package/src/styles/global.css +133 -0
  139. package/src/styles/input.css +54 -0
  140. package/src/styles/tokens.css +35 -0
  141. package/src/styles/utilities-buttons.css +125 -0
  142. package/src/styles/utilities-feedback.css +65 -0
  143. package/src/styles/utilities-layout.css +122 -0
  144. package/src/styles/utilities-navigation.css +196 -0
  145. package/src/types/ambient.d.ts +8 -0
  146. package/src/ui/admin-settings.tsx +148 -0
  147. package/src/ui/dialog-core.ts +146 -0
  148. package/src/ui/filter/FilterChip.tsx +196 -0
  149. package/src/ui/filter/index.ts +2 -0
  150. package/src/ui/index.ts +19 -0
  151. package/src/ui/input/Checkbox.tsx +55 -0
  152. package/src/ui/input/ColorInput.tsx +122 -0
  153. package/src/ui/input/DateTimeInput.tsx +86 -0
  154. package/src/ui/input/ImageInput.tsx +170 -0
  155. package/src/ui/input/NumberInput.tsx +113 -0
  156. package/src/ui/input/PinInput.tsx +169 -0
  157. package/src/ui/input/SegmentedControl.tsx +99 -0
  158. package/src/ui/input/Select.tsx +288 -0
  159. package/src/ui/input/SelectChip.tsx +61 -0
  160. package/src/ui/input/Slider.tsx +118 -0
  161. package/src/ui/input/Switch.tsx +62 -0
  162. package/src/ui/input/TagsInput.tsx +115 -0
  163. package/src/ui/input/TextInput.tsx +160 -0
  164. package/src/ui/input/index.ts +13 -0
  165. package/src/ui/input/types.ts +42 -0
  166. package/src/ui/input/util.tsx +105 -0
  167. package/src/ui/ipa/Avatar.tsx +28 -0
  168. package/src/ui/ipa/GroupView.tsx +36 -0
  169. package/src/ui/ipa/LoginBtn.tsx +16 -0
  170. package/src/ui/ipa/UserView.tsx +58 -0
  171. package/src/ui/ipa/index.ts +4 -0
  172. package/src/ui/misc/ContextMenu.tsx +211 -0
  173. package/src/ui/misc/CopyButton.tsx +28 -0
  174. package/src/ui/misc/Dropdown.tsx +194 -0
  175. package/src/ui/misc/EntitySearch.tsx +213 -0
  176. package/src/ui/misc/Lightbox.tsx +194 -0
  177. package/src/ui/misc/LinkCard.tsx +34 -0
  178. package/src/ui/misc/LogEntriesTable.tsx +61 -0
  179. package/src/ui/misc/MarkdownView.tsx +65 -0
  180. package/src/ui/misc/Pagination.tsx +51 -0
  181. package/src/ui/misc/PermissionEditor.tsx +379 -0
  182. package/src/ui/misc/ProgressBar.tsx +47 -0
  183. package/src/ui/misc/RemoveBtn.tsx +27 -0
  184. package/src/ui/misc/StatCell.tsx +90 -0
  185. package/src/ui/misc/index.ts +18 -0
  186. package/src/ui/navigation.ts +32 -0
  187. package/src/ui/prompts.tsx +854 -0
  188. package/src/ui/sidebar.tsx +468 -0
  189. package/src/ui/widgets/Widget.tsx +62 -0
  190. package/src/ui/widgets/WidgetCard.tsx +19 -0
  191. package/src/ui/widgets/WidgetHero.tsx +39 -0
  192. package/src/ui/widgets/WidgetList.tsx +84 -0
  193. package/src/ui/widgets/WidgetPills.tsx +68 -0
  194. package/src/ui/widgets/WidgetStat.tsx +67 -0
  195. package/src/ui/widgets/WidgetStatus.tsx +62 -0
  196. package/src/ui/widgets/index.ts +9 -0
@@ -0,0 +1,65 @@
1
+ /* Hover, cursor, and shimmer effects */
2
+ @utility hover-shadow {
3
+ &:hover {
4
+ box-shadow: var(--theme-shadow-hover);
5
+ }
6
+ }
7
+
8
+ @utility bg-dark {
9
+ @apply bg-[var(--color-dark)];
10
+ }
11
+
12
+ @utility no-scrollbar {
13
+ ::-webkit-scrollbar {
14
+ display: none; /* Chrome, Safari und Opera */
15
+ }
16
+
17
+ -ms-overflow-style: none; /* IE und Edge */
18
+ scrollbar-width: none; /* Firefox */
19
+ }
20
+
21
+ @utility ellipsis {
22
+ @apply overflow-hidden text-ellipsis whitespace-nowrap;
23
+ }
24
+
25
+ @utility animate-cursor {
26
+ animation: cursor-blink 1s step-end infinite;
27
+ }
28
+
29
+ @keyframes cursor-blink {
30
+ 0%,
31
+ 50% {
32
+ opacity: 1;
33
+ }
34
+ 51%,
35
+ 100% {
36
+ opacity: 0;
37
+ }
38
+ }
39
+
40
+ @utility shimmer {
41
+ position: relative;
42
+ overflow: hidden;
43
+
44
+ &::after {
45
+ content: "";
46
+ position: absolute;
47
+ inset: -50%;
48
+ background: linear-gradient(
49
+ 115deg,
50
+ transparent 0%,
51
+ rgba(255, 255, 255, 0.25) 50%,
52
+ transparent 100%
53
+ );
54
+ animation: shimmer 5s ease-in-out infinite;
55
+ }
56
+ }
57
+
58
+ @keyframes shimmer {
59
+ 0% {
60
+ transform: translateX(-100%);
61
+ }
62
+ 100% {
63
+ transform: translateX(100%);
64
+ }
65
+ }
@@ -0,0 +1,133 @@
1
+ @import "tailwindcss";
2
+ @plugin "@tailwindcss/typography";
3
+
4
+ /* global.css carries cloud-lib classes — UI primitives, layouts, shared
5
+ components — and is served to every container via the gateway. Apps must
6
+ NOT be scanned here; per-app classes come from each app's own app.css. */
7
+ @source "../**/*.{ts,tsx}";
8
+
9
+ @custom-variant dark (&:where(.dark, .dark *));
10
+ @custom-variant light (html:not(.dark) &);
11
+
12
+ @import "@tabler/icons-webfont/dist/tabler-icons.css";
13
+ @import "@fontsource-variable/jetbrains-mono";
14
+ @import "./tokens.css";
15
+ @import "./utilities-buttons.css";
16
+ @import "./utilities-layout.css";
17
+ @import "./utilities-navigation.css";
18
+ @import "./utilities-feedback.css";
19
+ @import "./base-popover.css";
20
+ @import "./effects.css";
21
+ @import "./input.css";
22
+
23
+ @theme {
24
+ --color-dark: rgb(9 9 11);
25
+ --color-selection-bg: rgb(147 197 253 / 0.45);
26
+ --color-selection-fg: rgb(15 23 42);
27
+ }
28
+
29
+ :root {
30
+ --theme-shadow-elevated: none;
31
+ --theme-shadow-hover: none;
32
+ --theme-input-py: 0.375rem;
33
+ --theme-input-px: 0.5rem;
34
+ --theme-list-active-border: none;
35
+ --theme-list-inactive-border: none;
36
+ --theme-list-active-bg: rgb(244 244 245);
37
+ --theme-list-hover-bg: rgb(250 250 250);
38
+ --theme-list-padding: 0.5rem 0.75rem;
39
+ --theme-tab-active-border: none;
40
+ --theme-tab-inactive-border: none;
41
+ --theme-tab-active-bg: rgb(244 244 245);
42
+ --theme-tab-hover-bg: rgb(250 250 250);
43
+ --theme-rail-item-active-border-r: none;
44
+ --theme-rail-item-inactive-border-r: none;
45
+ --theme-divider: 1px solid rgb(228 228 231);
46
+ }
47
+
48
+ * {
49
+ font-variant-ligatures: contextual;
50
+ font-family: "JetBrains Mono Variable", monospace;
51
+ scrollbar-width: thin;
52
+ scrollbar-color: rgb(161 161 170 / 0.4) transparent;
53
+ }
54
+
55
+ .dark * {
56
+ scrollbar-color: rgb(82 82 91 / 0.5) transparent;
57
+ }
58
+
59
+ *::-webkit-scrollbar {
60
+ width: 0.5rem;
61
+ height: 0.5rem;
62
+ }
63
+
64
+ *::-webkit-scrollbar-track {
65
+ background: transparent;
66
+ border-radius: 9999px;
67
+ }
68
+
69
+ *::-webkit-scrollbar-thumb {
70
+ background: rgb(161 161 170 / 0.35);
71
+ border-radius: 9999px;
72
+ border: 1px solid transparent;
73
+ background-clip: padding-box;
74
+ }
75
+
76
+ *::-webkit-scrollbar-thumb:hover {
77
+ background: rgb(161 161 170 / 0.55);
78
+ }
79
+
80
+ .dark *::-webkit-scrollbar-thumb {
81
+ background: rgb(82 82 91 / 0.5);
82
+ }
83
+
84
+ .dark *::-webkit-scrollbar-thumb:hover {
85
+ background: rgb(82 82 91 / 0.7);
86
+ }
87
+
88
+ *::-webkit-scrollbar-corner {
89
+ background: transparent;
90
+ }
91
+
92
+ body {
93
+ /* Explicit values — avoids @apply layer ordering issues with multiple CSS files */
94
+ background-color: rgb(250 250 250); /* zinc-50 */
95
+ color: rgb(24 24 27); /* zinc-900 */
96
+ }
97
+
98
+ .dark body {
99
+ background-color: rgb(9 9 11); /* zinc-950 */
100
+ color: rgb(244 244 245); /* zinc-100 */
101
+ }
102
+
103
+ .dark {
104
+ --theme-shadow-elevated: none;
105
+ --theme-shadow-hover: none;
106
+ --theme-list-active-bg: rgb(39 39 42);
107
+ --theme-list-hover-bg: rgb(30 30 33);
108
+ --theme-tab-active-bg: rgb(39 39 42);
109
+ --theme-tab-hover-bg: rgb(30 30 33);
110
+ --theme-divider: 1px solid rgb(39 39 42);
111
+ --color-selection-bg: rgb(59 130 246 / 0.5);
112
+ --color-selection-fg: rgb(239 246 255);
113
+ }
114
+
115
+ @view-transition {
116
+ navigation: auto;
117
+ }
118
+
119
+ ::selection {
120
+ background-color: var(--color-selection-bg);
121
+ color: var(--color-selection-fg);
122
+ }
123
+
124
+ ::-moz-selection {
125
+ background-color: var(--color-selection-bg);
126
+ color: var(--color-selection-fg);
127
+ }
128
+
129
+ /* SSR island wrappers should not break flex layout */
130
+ solid-client,
131
+ solid-island {
132
+ display: contents;
133
+ }
@@ -0,0 +1,54 @@
1
+ /* Global Button Styling */
2
+ button,
3
+ [role="button"] {
4
+ cursor: pointer;
5
+ }
6
+
7
+ button[disabled],
8
+ [role="button"][disabled] {
9
+ cursor: not-allowed;
10
+ opacity: 0.5;
11
+ }
12
+
13
+ [type="search"]::-webkit-search-cancel-button {
14
+ -webkit-appearance: none;
15
+ }
16
+
17
+ /* Global Checkbox Styling */
18
+ input[type="checkbox"] {
19
+ @apply appearance-none w-4 h-4 bg-white dark:bg-black border border-zinc-300 dark:border-zinc-700 cursor-pointer transition-all duration-200 inline-flex items-center justify-center m-0 align-middle rounded;
20
+ }
21
+
22
+ input[type="checkbox"]::before {
23
+ content: "\ea5e";
24
+ font-family: "tabler-icons";
25
+ @apply text-transparent text-xs leading-none transition-colors duration-200;
26
+ }
27
+
28
+ input[type="checkbox"]:checked {
29
+ @apply bg-blue-500 border-blue-500;
30
+ }
31
+
32
+ input[type="checkbox"]:checked::before {
33
+ @apply text-white;
34
+ }
35
+
36
+ input[type="checkbox"]:hover {
37
+ @apply scale-105;
38
+ }
39
+
40
+ input[type="checkbox"]:focus {
41
+ @apply outline-none;
42
+ }
43
+
44
+ input[type="checkbox"]:focus-visible {
45
+ @apply outline-none ring-2 ring-blue-500 ring-offset-2 dark:ring-offset-zinc-900;
46
+ }
47
+
48
+ input[type="checkbox"]:disabled {
49
+ @apply opacity-50 cursor-not-allowed;
50
+ }
51
+
52
+ input[type="checkbox"]:disabled:hover {
53
+ @apply scale-100;
54
+ }
@@ -0,0 +1,35 @@
1
+ /* Theme tokens and core text utilities */
2
+ @utility slider-track-colors {
3
+ --slider-fill: rgb(59 130 246);
4
+ --slider-track: rgb(228 228 231);
5
+ }
6
+
7
+ .dark .slider-track-colors,
8
+ .dark.slider-track-colors {
9
+ --slider-fill: rgb(96 165 250);
10
+ --slider-track: rgb(63 63 70);
11
+ }
12
+
13
+ @utility text-primary {
14
+ @apply text-zinc-900 dark:text-zinc-100;
15
+ }
16
+
17
+ @utility text-secondary {
18
+ @apply text-zinc-700 dark:text-zinc-300;
19
+ }
20
+
21
+ @utility text-dimmed {
22
+ @apply text-zinc-500 dark:text-zinc-500;
23
+ }
24
+
25
+ @utility text-label {
26
+ @apply text-sm font-medium text-zinc-700 dark:text-zinc-300;
27
+ }
28
+
29
+ @utility hover-text {
30
+ @apply text-zinc-400 hover:text-zinc-700 dark:text-zinc-600 dark:hover:text-zinc-300;
31
+ }
32
+
33
+ @utility bg-dark {
34
+ @apply bg-[var(--color-dark)];
35
+ }
@@ -0,0 +1,125 @@
1
+ /* Button and input utility classes — flat, solid */
2
+ @utility btn-base {
3
+ @apply inline-flex cursor-pointer items-center justify-center gap-1.5 select-none rounded-md font-medium border;
4
+ @apply transition-[background-color,color,border-color,opacity] duration-150 ease-out;
5
+ @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-zinc-950;
6
+ @apply disabled:cursor-not-allowed disabled:opacity-40 disabled:transition-none;
7
+ }
8
+
9
+ @utility btn-sm {
10
+ min-height: calc((var(--theme-input-py) * 2) + 1.25rem);
11
+ padding: var(--theme-input-py) 0.75rem;
12
+ @apply text-xs;
13
+ }
14
+
15
+ @utility btn-md {
16
+ min-height: calc((var(--theme-input-py) * 2) + 1.25rem + 0.55rem);
17
+ @apply px-4 py-2 text-sm;
18
+ }
19
+
20
+ /* Simple: ghost — no bg, no border, text only, bg on hover */
21
+ @utility btn-simple {
22
+ @apply btn-base bg-transparent text-zinc-600 border-transparent;
23
+ @apply hover:bg-zinc-100 hover:text-zinc-800;
24
+ @apply active:bg-zinc-200;
25
+ @apply dark:text-zinc-400 dark:border-transparent;
26
+ @apply dark:hover:bg-zinc-800 dark:hover:text-zinc-200;
27
+ @apply dark:active:bg-zinc-700;
28
+ }
29
+
30
+ /* Primary: outlined blue, fills on hover */
31
+ @utility btn-primary {
32
+ @apply btn-base bg-white text-blue-600 border-blue-400;
33
+ @apply hover:bg-blue-500 hover:text-white hover:border-blue-500;
34
+ @apply active:bg-blue-600 active:border-blue-600;
35
+ @apply dark:bg-transparent dark:text-blue-400 dark:border-blue-400;
36
+ @apply dark:hover:bg-blue-500 dark:hover:text-white dark:hover:border-blue-500;
37
+ @apply dark:active:bg-blue-600;
38
+ }
39
+
40
+ /* Secondary: flat zinc, no border */
41
+ @utility btn-secondary {
42
+ @apply btn-base bg-zinc-100 text-zinc-700 border-transparent;
43
+ @apply hover:bg-zinc-200;
44
+ @apply active:bg-zinc-300;
45
+ @apply dark:bg-zinc-800 dark:text-zinc-200 dark:border-transparent;
46
+ @apply dark:hover:bg-zinc-700;
47
+ @apply dark:active:bg-zinc-600;
48
+ }
49
+
50
+ /* Success: outlined green, fills on hover */
51
+ @utility btn-success {
52
+ @apply btn-base bg-white text-emerald-600 border-emerald-400;
53
+ @apply hover:bg-emerald-500 hover:text-white hover:border-emerald-500;
54
+ @apply active:bg-emerald-600 active:border-emerald-600;
55
+ @apply dark:bg-transparent dark:text-emerald-400 dark:border-emerald-400;
56
+ @apply dark:hover:bg-emerald-500 dark:hover:text-white dark:hover:border-emerald-500;
57
+ @apply dark:active:bg-emerald-600;
58
+ }
59
+
60
+ /* Danger: outlined red, fills on hover */
61
+ @utility btn-danger {
62
+ @apply btn-base bg-white text-red-600 border-red-400;
63
+ @apply hover:bg-red-500 hover:text-white hover:border-red-500;
64
+ @apply active:bg-red-600 active:border-red-600;
65
+ @apply dark:bg-transparent dark:text-red-400 dark:border-red-400;
66
+ @apply dark:hover:bg-red-500 dark:hover:text-white dark:hover:border-red-500;
67
+ @apply dark:active:bg-red-600;
68
+ }
69
+
70
+ @utility btn-input {
71
+ @apply inline-flex items-center justify-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium leading-5 select-none cursor-pointer;
72
+ @apply transition-[background-color,color,border-color] duration-150 border;
73
+ @apply bg-zinc-100 text-zinc-600 border-zinc-100;
74
+ @apply hover:bg-zinc-200/70 hover:text-zinc-800 hover:border-zinc-200/70;
75
+ @apply active:bg-zinc-200;
76
+ @apply focus-visible:outline-none focus-visible:bg-white focus-visible:border-blue-400;
77
+ @apply dark:bg-zinc-800 dark:text-zinc-300 dark:border-zinc-800;
78
+ @apply dark:hover:bg-zinc-700 dark:hover:text-zinc-100 dark:hover:border-zinc-700;
79
+ @apply dark:active:bg-zinc-700;
80
+ @apply dark:focus-visible:bg-zinc-900 dark:focus-visible:border-blue-400;
81
+ @apply disabled:cursor-not-allowed disabled:opacity-40 disabled:transition-none;
82
+ }
83
+
84
+ @utility btn-input-sm {
85
+ min-height: calc((var(--theme-input-py) * 2) + 1.25rem);
86
+ padding: var(--theme-input-py) 0.75rem;
87
+ @apply text-xs;
88
+ }
89
+
90
+ @utility btn-input-md {
91
+ @apply min-h-9 px-3 py-1.5 text-sm;
92
+ }
93
+
94
+ @utility btn-input-active {
95
+ @apply bg-blue-50 text-blue-600 border-blue-200;
96
+ @apply hover:bg-blue-100 hover:border-blue-200;
97
+ @apply dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800;
98
+ @apply dark:hover:bg-blue-900 dark:hover:border-blue-700;
99
+ }
100
+
101
+ /* Input: grey bg + matching border unfocused, white bg + blue border on focus */
102
+ @utility input {
103
+ padding: var(--theme-input-py) var(--theme-input-px);
104
+ @apply rounded-md border text-sm leading-5 focus-visible:outline-none;
105
+ @apply bg-zinc-100 text-zinc-800 border-zinc-100;
106
+ @apply hover:bg-zinc-200/70 hover:border-zinc-200/70;
107
+ @apply focus:!bg-white focus:!border-blue-400 focus-within:!bg-white focus-within:!border-blue-400;
108
+ @apply dark:bg-zinc-800 dark:text-zinc-200 dark:border-zinc-800;
109
+ @apply dark:hover:bg-zinc-700 dark:hover:border-zinc-700;
110
+ @apply dark:focus:!bg-zinc-900 dark:focus:!border-blue-400 dark:focus-within:!bg-zinc-900 dark:focus-within:!border-blue-400;
111
+ @apply disabled:bg-zinc-50 disabled:text-zinc-400 disabled:border-zinc-50 disabled:cursor-not-allowed;
112
+ @apply dark:disabled:bg-zinc-900 dark:disabled:text-zinc-500 dark:disabled:border-zinc-900;
113
+ }
114
+
115
+ @utility icon-btn {
116
+ @apply inline-flex h-8 w-8 items-center justify-center p-1 rounded-md;
117
+ @apply text-zinc-400 dark:text-zinc-500;
118
+ @apply transition-[color,background-color] duration-150 ease-out;
119
+ @apply bg-transparent border-0 shadow-none;
120
+ @apply hover:text-zinc-700 hover:bg-zinc-100;
121
+ @apply dark:hover:text-zinc-200 dark:hover:bg-zinc-800;
122
+ @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-zinc-950;
123
+ @apply active:bg-zinc-200 dark:active:bg-zinc-700;
124
+ @apply disabled:cursor-not-allowed disabled:opacity-40 disabled:transition-none;
125
+ }
@@ -0,0 +1,65 @@
1
+ /* Feedback blocks, badges, and chips — flat, solid, border-based */
2
+ @utility info-block {
3
+ @apply rounded-lg border px-3 py-2 text-sm leading-relaxed whitespace-pre-wrap;
4
+ }
5
+
6
+ @utility info-block-note {
7
+ @apply info-block bg-zinc-50 border-zinc-200 text-zinc-700 dark:bg-zinc-900 dark:border-zinc-700 dark:text-zinc-300;
8
+ }
9
+
10
+ @utility info-block-info {
11
+ @apply info-block bg-blue-50 border-blue-200 text-blue-800 dark:bg-blue-950 dark:border-blue-800 dark:text-blue-300;
12
+ }
13
+
14
+ @utility info-block-success {
15
+ @apply info-block bg-green-50 border-green-200 text-green-800 dark:bg-green-950 dark:border-green-800 dark:text-green-300;
16
+ }
17
+
18
+ @utility info-block-warning {
19
+ @apply info-block bg-amber-50 border-amber-200 text-amber-800 dark:bg-amber-950 dark:border-amber-800 dark:text-amber-300;
20
+ }
21
+
22
+ @utility info-block-danger {
23
+ @apply info-block bg-red-50 border-red-200 text-red-800 dark:bg-red-950 dark:border-red-800 dark:text-red-300;
24
+ }
25
+
26
+ @utility status-dot {
27
+ @apply w-2 h-2 rounded-full shrink-0;
28
+ }
29
+
30
+ @utility badge {
31
+ @apply rounded inline-flex items-center gap-1 px-1.5 py-0.5 text-xs;
32
+ }
33
+
34
+ @utility chip {
35
+ @apply rounded-lg inline-flex items-center gap-1.5 text-xs transition-colors cursor-pointer px-2 py-1;
36
+ @apply border border-zinc-200 dark:border-zinc-700;
37
+ }
38
+
39
+ @utility thumbnail {
40
+ @apply rounded-lg overflow-hidden;
41
+ }
42
+
43
+ @utility popup {
44
+ @apply rounded-lg bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 shadow-lg text-zinc-900 dark:text-zinc-100;
45
+ }
46
+
47
+ @utility tag {
48
+ @apply rounded inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium;
49
+ }
50
+
51
+ /* Stat cards — markup-only convention, see
52
+ `skills/cloud-app/references/frontend.md` § Stats and the live demos in
53
+ `packages/ui-lab/src/frontend/UiLabShowcase.island.tsx`. Pattern:
54
+ <div class="paper overflow-hidden">
55
+ <div class="grid grid-cols-N gap-px p-px bg-zinc-100 dark:bg-zinc-800">
56
+ <div class="bg-white dark:bg-zinc-900 px-4 py-4 flex flex-col gap-0.5">
57
+ <span class="text-[10px] uppercase tracking-wider text-dimmed">Label</span>
58
+ <span class="text-xl font-bold tabular-nums text-primary">Value</span>
59
+ <span class="text-[10px] text-dimmed">Sub</span>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ The earlier `.stat-grid / .stat-cell / .stat-value / .stat-label / .stat-sub`
64
+ utilities used `divide-x` + centered cells — DO NOT reintroduce. They
65
+ produced double borders at corners and missing borders on edges. */
@@ -0,0 +1,122 @@
1
+ /* Layout, containers, dividers, and prose styles — flat, solid surfaces */
2
+ @utility section {
3
+ @apply py-5;
4
+ }
5
+
6
+ @utility article {
7
+ @apply mx-auto max-w-[65ch] px-2 py-4 pb-10 transition-[max-width] duration-500 ease-in-out md:px-4 lg:max-w-[80ch] [&_img]:mx-auto [&_img]:max-h-[50vh] [&_img]:w-auto;
8
+
9
+ & img {
10
+ @apply rounded-lg;
11
+ }
12
+ }
13
+
14
+ @utility container {
15
+ @apply mx-auto max-w-480 px-4;
16
+ }
17
+
18
+ @utility paper {
19
+ @apply rounded-lg transition-colors duration-150;
20
+ @apply bg-white border border-zinc-100;
21
+ @apply dark:bg-zinc-900 dark:border-zinc-800;
22
+ }
23
+
24
+ @utility dialog-panel {
25
+ @apply fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2;
26
+ @apply w-full max-w-md max-h-[90vh] p-4 overflow-x-hidden overflow-y-auto;
27
+ @apply backdrop:bg-black/40;
28
+ @apply rounded-lg bg-white dark:bg-zinc-900;
29
+ @apply border border-zinc-200 dark:border-zinc-800;
30
+ @apply shadow-lg;
31
+ }
32
+
33
+ @utility paper-highlighted {
34
+ @apply paper border-zinc-300 dark:border-zinc-700;
35
+ }
36
+
37
+ @utility no-scrollbar {
38
+ ::-webkit-scrollbar {
39
+ display: none;
40
+ }
41
+
42
+ -ms-overflow-style: none;
43
+ scrollbar-width: none;
44
+ }
45
+
46
+ @utility ellipsis {
47
+ @apply overflow-hidden text-ellipsis whitespace-nowrap;
48
+ }
49
+
50
+ @utility divider {
51
+ border: none;
52
+ border-bottom: var(--theme-divider);
53
+ @apply hidden;
54
+ }
55
+
56
+ @utility divider-v {
57
+ border: none;
58
+ border-right: var(--theme-divider);
59
+ }
60
+
61
+ @utility app-cols {
62
+ @apply flex flex-col gap-2 lg:flex-row lg:items-stretch lg:gap-2;
63
+ }
64
+
65
+ @utility app-rows {
66
+ @apply flex flex-col gap-3;
67
+ }
68
+
69
+ @utility app-divider {
70
+ & > * + * {
71
+ border-top: var(--theme-divider);
72
+ }
73
+ }
74
+
75
+ .prose {
76
+ @apply text-zinc-900 dark:text-zinc-100;
77
+
78
+ h1 {
79
+ @apply text-3xl font-bold mb-4 mt-8 first:mt-0 text-zinc-900 dark:text-zinc-100;
80
+ }
81
+
82
+ h2 {
83
+ @apply text-2xl font-bold mb-3 mt-6 text-zinc-900 dark:text-zinc-100;
84
+ }
85
+
86
+ h3 {
87
+ @apply text-xl font-semibold mb-2 mt-4 text-zinc-900 dark:text-zinc-100;
88
+ }
89
+
90
+ p {
91
+ @apply mb-4 leading-7;
92
+ }
93
+
94
+ ul,
95
+ ol {
96
+ @apply mb-4 ml-6 space-y-2;
97
+ }
98
+
99
+ ul {
100
+ @apply list-disc;
101
+ }
102
+
103
+ ol {
104
+ @apply list-decimal;
105
+ }
106
+
107
+ li {
108
+ @apply leading-7;
109
+ }
110
+
111
+ a {
112
+ @apply text-blue-600 hover:text-blue-700 underline dark:text-blue-400 dark:hover:text-blue-300;
113
+ }
114
+
115
+ strong {
116
+ @apply font-semibold text-zinc-900 dark:text-zinc-100;
117
+ }
118
+
119
+ hr {
120
+ @apply my-8 border-zinc-200 dark:border-zinc-800;
121
+ }
122
+ }