@moloodev/ui 0.0.0-stage → 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 (93) hide show
  1. package/package.json +139 -3
  2. package/registry/components.ts +135 -0
  3. package/registry/fonts.css +81 -0
  4. package/registry/suit-OFL.txt +94 -0
  5. package/registry/wanted-sans-OFL.txt +93 -0
  6. package/src/components/ui/accordion.tsx +74 -0
  7. package/src/components/ui/alert-dialog.tsx +187 -0
  8. package/src/components/ui/alert.tsx +83 -0
  9. package/src/components/ui/aspect-ratio.tsx +22 -0
  10. package/src/components/ui/attachment.tsx +207 -0
  11. package/src/components/ui/avatar.tsx +109 -0
  12. package/src/components/ui/badge.tsx +54 -0
  13. package/src/components/ui/breadcrumb.tsx +126 -0
  14. package/src/components/ui/bubble.tsx +128 -0
  15. package/src/components/ui/button-group.tsx +103 -0
  16. package/src/components/ui/button.tsx +68 -0
  17. package/src/components/ui/calendar.tsx +222 -0
  18. package/src/components/ui/card.tsx +103 -0
  19. package/src/components/ui/carousel.tsx +242 -0
  20. package/src/components/ui/chart.tsx +398 -0
  21. package/src/components/ui/checkbox.tsx +28 -0
  22. package/src/components/ui/collapsible.tsx +40 -0
  23. package/src/components/ui/combobox.tsx +299 -0
  24. package/src/components/ui/command.tsx +218 -0
  25. package/src/components/ui/context-menu.tsx +271 -0
  26. package/src/components/ui/data-table.tsx +919 -0
  27. package/src/components/ui/date-picker.tsx +65 -0
  28. package/src/components/ui/dialog.tsx +224 -0
  29. package/src/components/ui/drawer.tsx +231 -0
  30. package/src/components/ui/dropdown-menu.tsx +268 -0
  31. package/src/components/ui/dropzone.tsx +186 -0
  32. package/src/components/ui/empty.tsx +101 -0
  33. package/src/components/ui/field.tsx +238 -0
  34. package/src/components/ui/form.tsx +83 -0
  35. package/src/components/ui/highlighted-source.tsx +200 -0
  36. package/src/components/ui/hover-card.tsx +51 -0
  37. package/src/components/ui/image-field.tsx +796 -0
  38. package/src/components/ui/input-group.tsx +149 -0
  39. package/src/components/ui/input-otp.tsx +86 -0
  40. package/src/components/ui/input-step.ts +19 -0
  41. package/src/components/ui/input.tsx +100 -0
  42. package/src/components/ui/item.tsx +201 -0
  43. package/src/components/ui/kbd.tsx +91 -0
  44. package/src/components/ui/kind-icon-glob.ts +24 -0
  45. package/src/components/ui/kind-icon-load.ts +52 -0
  46. package/src/components/ui/kind-icon-static.ts +46 -0
  47. package/src/components/ui/kind-icon.tsx +84 -0
  48. package/src/components/ui/kind-mark.tsx +65 -0
  49. package/src/components/ui/label.tsx +29 -0
  50. package/src/components/ui/marker.tsx +71 -0
  51. package/src/components/ui/menubar.tsx +277 -0
  52. package/src/components/ui/message-scroller.tsx +137 -0
  53. package/src/components/ui/message.tsx +92 -0
  54. package/src/components/ui/moloo-logo.tsx +45 -0
  55. package/src/components/ui/native-select.tsx +61 -0
  56. package/src/components/ui/navigation-menu.tsx +168 -0
  57. package/src/components/ui/outline-list.tsx +73 -0
  58. package/src/components/ui/pagination.tsx +130 -0
  59. package/src/components/ui/panel.tsx +108 -0
  60. package/src/components/ui/popover.tsx +90 -0
  61. package/src/components/ui/progress.tsx +117 -0
  62. package/src/components/ui/radio-group.tsx +38 -0
  63. package/src/components/ui/resizable.tsx +52 -0
  64. package/src/components/ui/scroll-area.tsx +54 -0
  65. package/src/components/ui/select.tsx +205 -0
  66. package/src/components/ui/separator.tsx +25 -0
  67. package/src/components/ui/sheet.tsx +140 -0
  68. package/src/components/ui/sidebar.tsx +746 -0
  69. package/src/components/ui/skeleton.tsx +13 -0
  70. package/src/components/ui/slider.tsx +52 -0
  71. package/src/components/ui/spinner.tsx +18 -0
  72. package/src/components/ui/switch.tsx +32 -0
  73. package/src/components/ui/table.tsx +116 -0
  74. package/src/components/ui/tabs.tsx +83 -0
  75. package/src/components/ui/tag-list-input.tsx +194 -0
  76. package/src/components/ui/textarea.tsx +18 -0
  77. package/src/components/ui/timeline.tsx +265 -0
  78. package/src/components/ui/toast.tsx +237 -0
  79. package/src/components/ui/toggle-group.tsx +90 -0
  80. package/src/components/ui/toggle.tsx +45 -0
  81. package/src/components/ui/tooltip.tsx +66 -0
  82. package/src/css.d.ts +1 -0
  83. package/src/hooks/use-clipboard.ts +82 -0
  84. package/src/hooks/use-mobile.ts +21 -0
  85. package/src/index.ts +106 -0
  86. package/src/kind-icon.ts +4 -0
  87. package/src/lib/format-relative-time.ts +41 -0
  88. package/src/lib/list-selection.ts +61 -0
  89. package/src/lib/utils.ts +6 -0
  90. package/src/moloo.css +360 -0
  91. package/src/styles.css +36 -0
  92. package/src/typeset.css +518 -0
  93. package/README.md +0 -3
@@ -0,0 +1,82 @@
1
+ import { useEffect, useRef, useState } from "react"
2
+
3
+ const DEFAULT_ERROR_MESSAGE = "Could not copy to the clipboard."
4
+
5
+ export type UseClipboardOptions = {
6
+ /** How long `copied` stays true after a successful copy. */
7
+ resetMs?: number
8
+ onSuccess?: (text: string) => void
9
+ onError?: (error: Error) => void
10
+ /**
11
+ * Message when the fallback copy fails, or when the failure is not an Error.
12
+ * A clipboard API rejection keeps that Error.
13
+ */
14
+ errorMessage?: string
15
+ }
16
+
17
+ export type UseClipboardResult = {
18
+ copy: (text: string) => Promise<boolean>
19
+ copied: boolean
20
+ error: Error | null
21
+ }
22
+
23
+ async function writeClipboard(text: string, errorMessage: string) {
24
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
25
+ await navigator.clipboard.writeText(text)
26
+ return
27
+ }
28
+
29
+ const textarea = document.createElement("textarea")
30
+ textarea.value = text
31
+ textarea.setAttribute("readonly", "")
32
+ textarea.style.position = "fixed"
33
+ textarea.style.opacity = "0"
34
+ document.body.appendChild(textarea)
35
+ textarea.select()
36
+ const ok = document.execCommand("copy")
37
+ document.body.removeChild(textarea)
38
+ if (!ok) {
39
+ throw new Error(errorMessage)
40
+ }
41
+ }
42
+
43
+ /** Copy text to the system clipboard with a short-lived `copied` flag. */
44
+ export function useClipboard(
45
+ options: UseClipboardOptions = {},
46
+ ): UseClipboardResult {
47
+ const {
48
+ resetMs = 1500,
49
+ onSuccess,
50
+ onError,
51
+ errorMessage = DEFAULT_ERROR_MESSAGE,
52
+ } = options
53
+ const [copied, setCopied] = useState(false)
54
+ const [error, setError] = useState<Error | null>(null)
55
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
56
+
57
+ useEffect(() => {
58
+ return () => {
59
+ if (timerRef.current) clearTimeout(timerRef.current)
60
+ }
61
+ }, [])
62
+
63
+ async function copy(text: string) {
64
+ try {
65
+ await writeClipboard(text, errorMessage)
66
+ setError(null)
67
+ setCopied(true)
68
+ onSuccess?.(text)
69
+ if (timerRef.current) clearTimeout(timerRef.current)
70
+ timerRef.current = setTimeout(() => setCopied(false), resetMs)
71
+ return true
72
+ } catch (cause) {
73
+ const next = cause instanceof Error ? cause : new Error(errorMessage)
74
+ setError(next)
75
+ setCopied(false)
76
+ onError?.(next)
77
+ return false
78
+ }
79
+ }
80
+
81
+ return { copy, copied, error }
82
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from "react"
2
+
3
+ const MOBILE_BREAKPOINT = 768
4
+
5
+ function subscribe(onStoreChange: () => void) {
6
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
7
+ mql.addEventListener("change", onStoreChange)
8
+ return () => mql.removeEventListener("change", onStoreChange)
9
+ }
10
+
11
+ function getSnapshot() {
12
+ return window.innerWidth < MOBILE_BREAKPOINT
13
+ }
14
+
15
+ function getServerSnapshot() {
16
+ return false
17
+ }
18
+
19
+ export function useIsMobile() {
20
+ return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
21
+ }
package/src/index.ts ADDED
@@ -0,0 +1,106 @@
1
+ // Generated from the moloo UI kit. Subpath exports mirror these modules.
2
+ export * from "./components/ui/accordion"
3
+ export * from "./components/ui/alert-dialog"
4
+ export * from "./components/ui/alert"
5
+ export * from "./components/ui/aspect-ratio"
6
+ export * from "./components/ui/attachment"
7
+ export * from "./components/ui/avatar"
8
+ export * from "./components/ui/badge"
9
+ export * from "./components/ui/breadcrumb"
10
+ export * from "./components/ui/bubble"
11
+ export * from "./components/ui/button-group"
12
+ export * from "./components/ui/button"
13
+ export * from "./components/ui/calendar"
14
+ export * from "./components/ui/card"
15
+ export * from "./components/ui/carousel"
16
+ export * from "./components/ui/chart"
17
+ export * from "./components/ui/checkbox"
18
+ export * from "./components/ui/collapsible"
19
+ export * from "./components/ui/combobox"
20
+ export * from "./components/ui/command"
21
+ export * from "./components/ui/context-menu"
22
+ export * from "./components/ui/date-picker"
23
+ export * from "./components/ui/dialog"
24
+ export * from "./components/ui/drawer"
25
+ export * from "./components/ui/dropdown-menu"
26
+ export * from "./components/ui/dropzone"
27
+ export * from "./components/ui/empty"
28
+ export * from "./components/ui/field"
29
+ export * from "./components/ui/form"
30
+ export * from "./components/ui/highlighted-source"
31
+ export * from "./components/ui/hover-card"
32
+ export * from "./components/ui/image-field"
33
+ export * from "./components/ui/input-group"
34
+ export * from "./components/ui/input-otp"
35
+ export * from "./components/ui/input"
36
+ export * from "./components/ui/item"
37
+ export * from "./components/ui/kbd"
38
+ export * from "./components/ui/label"
39
+ export * from "./components/ui/marker"
40
+ export * from "./components/ui/menubar"
41
+ export * from "./components/ui/message-scroller"
42
+ export * from "./components/ui/message"
43
+ export * from "./components/ui/moloo-logo"
44
+ export * from "./components/ui/native-select"
45
+ export * from "./components/ui/navigation-menu"
46
+ export * from "./components/ui/outline-list"
47
+ export * from "./components/ui/pagination"
48
+ export * from "./components/ui/panel"
49
+ export * from "./components/ui/popover"
50
+ export * from "./components/ui/progress"
51
+ export * from "./components/ui/radio-group"
52
+ export * from "./components/ui/resizable"
53
+ export * from "./components/ui/scroll-area"
54
+ export * from "./components/ui/select"
55
+ export * from "./components/ui/separator"
56
+ export * from "./components/ui/sheet"
57
+ export * from "./components/ui/sidebar"
58
+ export * from "./components/ui/skeleton"
59
+ export * from "./components/ui/slider"
60
+ export * from "./components/ui/spinner"
61
+ export * from "./components/ui/switch"
62
+ export * from "./components/ui/table"
63
+ export * from "./components/ui/tabs"
64
+ export * from "./components/ui/tag-list-input"
65
+ export * from "./components/ui/textarea"
66
+ export * from "./components/ui/timeline"
67
+ export * from "./components/ui/toast"
68
+ export * from "./components/ui/toggle-group"
69
+ export * from "./components/ui/toggle"
70
+ export * from "./components/ui/tooltip"
71
+ export {
72
+ BulkBar,
73
+ Root,
74
+ Toolbar,
75
+ allEnumFilterValues,
76
+ appFeatures,
77
+ compareBySorting,
78
+ compareValues,
79
+ createColumnHelper,
80
+ features,
81
+ isEnumFilterNarrowed,
82
+ searchAcrossFields,
83
+ sortBySortingState,
84
+ toggleEnumFilterValue,
85
+ useCellContext,
86
+ useHeaderContext,
87
+ useTable,
88
+ useTableContext,
89
+ } from "./components/ui/data-table"
90
+ export type {
91
+ ColumnFiltersState,
92
+ ColumnMetaWithCopyable,
93
+ ColumnMetaWithEnumFilter,
94
+ EnumFilterMeta,
95
+ EnumFilterOption,
96
+ PaginationState,
97
+ RowSelectionState,
98
+ SearchAcrossFieldsInput,
99
+ SearchAcrossFieldsResult,
100
+ SortingState,
101
+ } from "./components/ui/data-table"
102
+ export * from "./hooks/use-clipboard"
103
+ export * from "./hooks/use-mobile"
104
+ export * from "./lib/utils"
105
+ export * from "./lib/format-relative-time"
106
+ export * from "./lib/list-selection"
@@ -0,0 +1,4 @@
1
+ "use client"
2
+
3
+ export { KindIcon, type KindIconProps } from "./components/ui/kind-icon"
4
+ export { KindMark, type KindMarkProps } from "./components/ui/kind-mark"
@@ -0,0 +1,41 @@
1
+ const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [
2
+ ["year", 365 * 24 * 60 * 60 * 1000],
3
+ ["day", 24 * 60 * 60 * 1000],
4
+ ["hour", 60 * 60 * 1000],
5
+ ["minute", 60 * 1000],
6
+ ["second", 1000],
7
+ ]
8
+
9
+ const formatters = new Map<string, Intl.RelativeTimeFormat>()
10
+
11
+ function relativeTimeFormat(locale: string) {
12
+ const cached = formatters.get(locale)
13
+ if (cached) return cached
14
+ const formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" })
15
+ formatters.set(locale, formatter)
16
+ return formatter
17
+ }
18
+
19
+ function toTime(value: string | Date | number) {
20
+ if (value instanceof Date) return value.getTime()
21
+ if (typeof value === "number") return value
22
+ return new Date(value).getTime()
23
+ }
24
+
25
+ /** Locale-aware past/future relative time via `Intl.RelativeTimeFormat`. */
26
+ export function formatRelativeTime(
27
+ value: string | Date | number,
28
+ now = Date.now(),
29
+ locale = "en",
30
+ ): string {
31
+ const then = toTime(value)
32
+ if (!Number.isFinite(then)) return ""
33
+ const elapsed = then - now
34
+ const formatter = relativeTimeFormat(locale)
35
+ for (const [unit, ms] of UNITS) {
36
+ if (Math.abs(elapsed) >= ms || unit === "second") {
37
+ return formatter.format(Math.round(elapsed / ms), unit)
38
+ }
39
+ }
40
+ return formatter.format(0, "second")
41
+ }
@@ -0,0 +1,61 @@
1
+ export function isToggleModifier(event: {
2
+ metaKey: boolean;
3
+ ctrlKey: boolean;
4
+ }): boolean {
5
+ return event.metaKey || event.ctrlKey;
6
+ }
7
+
8
+ export function applyListSelection(input: {
9
+ visibleIds: readonly string[];
10
+ checkedIds: ReadonlySet<string>;
11
+ anchorId: string | null;
12
+ clickedId: string;
13
+ toggle: boolean;
14
+ range: boolean;
15
+ }): { checkedIds: Set<string>; anchorId: string | null } {
16
+ const { visibleIds, checkedIds, clickedId, toggle, range } = input;
17
+ const anchorIndex =
18
+ input.anchorId == null ? -1 : visibleIds.indexOf(input.anchorId);
19
+ const clickedIndex = visibleIds.indexOf(clickedId);
20
+
21
+ if (range) {
22
+ if (anchorIndex < 0 || clickedIndex < 0) {
23
+ return { checkedIds: new Set([clickedId]), anchorId: clickedId };
24
+ }
25
+ const from = Math.min(anchorIndex, clickedIndex);
26
+ const to = Math.max(anchorIndex, clickedIndex);
27
+ const rangeIds = visibleIds.slice(from, to + 1);
28
+ const next = toggle ? new Set(checkedIds) : new Set<string>();
29
+ for (const id of rangeIds) next.add(id);
30
+ return { checkedIds: next, anchorId: input.anchorId };
31
+ }
32
+
33
+ if (toggle) {
34
+ const next = new Set(checkedIds);
35
+ if (next.has(clickedId)) next.delete(clickedId);
36
+ else next.add(clickedId);
37
+ return { checkedIds: next, anchorId: clickedId };
38
+ }
39
+
40
+ return { checkedIds: new Set(checkedIds), anchorId: clickedId };
41
+ }
42
+
43
+ /**
44
+ * Keyboard cursor for an asset list <tr>.
45
+ *
46
+ * Safari ignores `position: relative` on table-row, so an absolutely
47
+ * positioned `::after` on the row uses the dialog (`position: fixed`) as
48
+ * its containing block — a focus ring around the dialog instead of the
49
+ * row. Paint the ring with inset box-shadow on the cells instead.
50
+ * The list table uses `border-separate` so Safari does not clip those
51
+ * inset shadows against collapsed row borders.
52
+ */
53
+ export function assetRowKeyboardRingClass(active: boolean): string {
54
+ if (!active) return "";
55
+ return [
56
+ "z-[1]",
57
+ "[&>td]:shadow-[inset_0_2px_0_0_var(--primary),inset_0_-2px_0_0_var(--primary)]",
58
+ "[&>td:first-child]:shadow-[inset_2px_0_0_0_var(--primary),inset_0_2px_0_0_var(--primary),inset_0_-2px_0_0_var(--primary)]",
59
+ "[&>td:last-child]:shadow-[inset_-2px_0_0_0_var(--primary),inset_0_2px_0_0_var(--primary),inset_0_-2px_0_0_var(--primary)]",
60
+ ].join(" ");
61
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
package/src/moloo.css ADDED
@@ -0,0 +1,360 @@
1
+ @theme inline {
2
+ --color-background: var(--background);
3
+ --color-foreground: var(--foreground);
4
+ --color-apricot: var(--apricot);
5
+ --color-field-bg: var(--field-bg);
6
+ --font-sans: "SUIT", ui-sans-serif, system-ui, sans-serif;
7
+ --font-display: "WantedSans", "SUIT", ui-sans-serif, system-ui, sans-serif;
8
+ --font-heading: var(--font-display);
9
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
10
+ --color-sidebar-ring: var(--sidebar-ring);
11
+ --color-sidebar-border: var(--sidebar-border);
12
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
13
+ --color-sidebar-accent: var(--sidebar-accent);
14
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
15
+ --color-sidebar-primary: var(--sidebar-primary);
16
+ --color-sidebar-foreground: var(--sidebar-foreground);
17
+ --color-sidebar: var(--sidebar);
18
+ --color-chart-5: var(--chart-5);
19
+ --color-chart-4: var(--chart-4);
20
+ --color-chart-3: var(--chart-3);
21
+ --color-chart-2: var(--chart-2);
22
+ --color-chart-1: var(--chart-1);
23
+ --color-chart-axis: var(--chart-axis);
24
+ --color-chart-grid: var(--chart-grid);
25
+ --color-chart-cursor: var(--chart-cursor);
26
+ --color-ring: var(--ring);
27
+ --color-input: var(--input);
28
+ --color-border: var(--border);
29
+ --color-destructive-foreground: var(--destructive-foreground);
30
+ --color-destructive-fill: var(--destructive-fill);
31
+ --color-destructive: var(--destructive);
32
+ --color-accent-foreground: var(--accent-foreground);
33
+ --color-accent: var(--accent);
34
+ --color-muted-foreground: var(--muted-foreground);
35
+ --color-muted: var(--muted);
36
+ --color-secondary-foreground: var(--secondary-foreground);
37
+ --color-secondary: var(--secondary);
38
+ --color-primary-foreground: var(--primary-foreground);
39
+ --color-primary-hover: var(--primary-hover);
40
+ --color-primary: var(--primary);
41
+ --color-popover-foreground: var(--popover-foreground);
42
+ --color-popover: var(--popover);
43
+ --color-card-foreground: var(--card-foreground);
44
+ --color-card: var(--card);
45
+ --radius-sm: calc(var(--radius) - 2px);
46
+ --radius-md: var(--radius);
47
+ --radius-lg: calc(var(--radius) + 2px);
48
+ --radius-xl: calc(var(--radius) + 4px);
49
+ --radius-2xl: calc(var(--radius) + 8px);
50
+ --radius-3xl: calc(var(--radius) + 12px);
51
+ --radius-4xl: calc(var(--radius) + 16px);
52
+ --shadow-moloo: var(--moloo-shadow);
53
+ --shadow-moloo-hover: var(--moloo-shadow-hover);
54
+ --shadow-moloo-active: var(--moloo-shadow-active);
55
+ --shadow-moloo-lg: var(--moloo-shadow-lg);
56
+ --moloo-shadow-sm: var(--moloo-shadow-sm);
57
+ }
58
+
59
+ :root {
60
+ --background: #ffffff;
61
+ --foreground: #12141a;
62
+ --card: #f8fafc;
63
+ --card-foreground: #12141a;
64
+ --popover: #f8fafc;
65
+ --popover-foreground: #12141a;
66
+ --primary: #0e9aa0;
67
+ --primary-foreground: #f4f6f8;
68
+ --secondary: #cdd5e0;
69
+ --secondary-foreground: #12141a;
70
+ --muted: #e6ebf1;
71
+ --muted-foreground: #5a6675;
72
+ --accent: #ffb86b;
73
+ --accent-foreground: #1a1208;
74
+ --apricot: #ffb86b;
75
+ --destructive: #e0457a;
76
+ --destructive-foreground: #ffffff;
77
+ --destructive-fill: #dc2f6a;
78
+ --primary-hover: #0b7b80;
79
+ --border: #c9d1dc;
80
+ --input: #c9d1dc;
81
+ --ring: #0e9aa0;
82
+ --chart-1: #0e9aa0;
83
+ --chart-2: #ffb86b;
84
+ --chart-3: #e0457a;
85
+ --chart-4: #5a6675;
86
+ --chart-5: #222836;
87
+ --chart-axis: #3d4654;
88
+ --chart-grid: #8a95a6;
89
+ --chart-cursor: color-mix(in oklab, var(--foreground) 7%, transparent);
90
+ --radius: 0.35rem;
91
+ --sidebar: #f8fafc;
92
+ --sidebar-foreground: #12141a;
93
+ --sidebar-primary: #0e9aa0;
94
+ --sidebar-primary-foreground: #f4f6f8;
95
+ --sidebar-accent: #e6ebf1;
96
+ --sidebar-accent-foreground: #12141a;
97
+ --sidebar-border: #c9d1dc;
98
+ --sidebar-ring: #0e9aa0;
99
+ --moloo-shadow: 3px 3px 0 rgba(14, 90, 96, 0.08);
100
+ --moloo-shadow-hover: 4px 4px 0 rgba(14, 90, 96, 0.08);
101
+ --moloo-shadow-active: 1px 1px 0 rgba(14, 90, 96, 0.08);
102
+ --moloo-shadow-lg: 4px 4px 0 rgba(14, 90, 96, 0.09);
103
+ --moloo-shadow-sm: 2px 2px 0 rgba(14, 90, 96, 0.06);
104
+ --moloo-progress-shadow: inset 0 0 0 1px var(--border), 2px 2px 0 rgba(0, 0, 0, 0.2);
105
+ --moloo-invalid-shadow: 3px 3px 0 color-mix(in srgb, var(--destructive) 35%, transparent);
106
+ --field-bg: #f0f3f7;
107
+ --label-font: var(--font-display);
108
+ --label-transform: none;
109
+ --label-tracking: -0.02em;
110
+ --label-weight: 700;
111
+ }
112
+
113
+ .dark {
114
+ --background: #12141a;
115
+ --foreground: #eef3f7;
116
+ --card: #1a1e28;
117
+ --card-foreground: #eef3f7;
118
+ --popover: #1a1e28;
119
+ --popover-foreground: #eef3f7;
120
+ --primary: #5ce1e6;
121
+ --primary-foreground: #071215;
122
+ --secondary: #303848;
123
+ --secondary-foreground: #eef3f7;
124
+ --muted: #222836;
125
+ --muted-foreground: #9aa6b5;
126
+ --accent: #ffb86b;
127
+ --accent-foreground: #1a1208;
128
+ --apricot: #ffb86b;
129
+ --destructive: #ff6b9d;
130
+ --destructive-foreground: #071215;
131
+ --destructive-fill: #ff6b9d;
132
+ --primary-hover: #7aedf1;
133
+ --border: #2a3140;
134
+ --input: #2a3140;
135
+ --ring: #5ce1e6;
136
+ --chart-1: #5ce1e6;
137
+ --chart-2: #ffb86b;
138
+ --chart-3: #ff6b9d;
139
+ --chart-4: #9aa6b5;
140
+ --chart-5: #eef3f7;
141
+ --chart-axis: #eef3f7;
142
+ --chart-grid: #6b7688;
143
+ --chart-cursor: color-mix(in oklab, var(--foreground) 10%, transparent);
144
+ --sidebar: #1a1e28;
145
+ --sidebar-foreground: #eef3f7;
146
+ --sidebar-primary: #5ce1e6;
147
+ --sidebar-primary-foreground: #071215;
148
+ --sidebar-accent: #222836;
149
+ --sidebar-accent-foreground: #eef3f7;
150
+ --sidebar-border: #2a3140;
151
+ --sidebar-ring: #5ce1e6;
152
+ --moloo-shadow: 3px 3px 0 rgba(0, 0, 0, 0.35);
153
+ --moloo-shadow-hover: 4px 4px 0 rgba(0, 0, 0, 0.35);
154
+ --moloo-shadow-active: 1px 1px 0 rgba(0, 0, 0, 0.35);
155
+ --moloo-shadow-lg: 4px 4px 0 rgba(0, 0, 0, 0.3);
156
+ --moloo-shadow-sm: 2px 2px 0 rgba(0, 0, 0, 0.28);
157
+ --field-bg: #0e1218;
158
+ }
159
+
160
+ @layer base {
161
+ /*
162
+ Tailwind v4 preflight sets `border: 0 solid`, which paints bare
163
+ border utilities in currentColor. The moloo-ui kit overrode that
164
+ with `* { @apply border-border }` in app globals; the rule was
165
+ dropped when the kit moved here, so separators inherited the text
166
+ color. Utilities such as border-border still win over this layer.
167
+ */
168
+ *,
169
+ ::after,
170
+ ::before,
171
+ ::backdrop,
172
+ ::file-selector-button {
173
+ border-color: var(--border);
174
+ }
175
+
176
+ h1,
177
+ h2,
178
+ h3,
179
+ h4,
180
+ .font-display {
181
+ font-family: var(--font-display);
182
+ letter-spacing: -0.03em;
183
+ }
184
+ button:not(:disabled),
185
+ [role="button"]:not(:disabled) {
186
+ cursor: pointer;
187
+ }
188
+ }
189
+
190
+ @layer utilities {
191
+ .label {
192
+ font-family: var(--label-font);
193
+ font-size: 0.875rem;
194
+ line-height: 1;
195
+ font-weight: var(--label-weight);
196
+ text-transform: var(--label-transform);
197
+ letter-spacing: var(--label-tracking);
198
+ }
199
+
200
+ .shadow-moloo {
201
+ box-shadow: var(--moloo-shadow);
202
+ }
203
+
204
+ .shadow-moloo-sm {
205
+ box-shadow: var(--moloo-shadow-sm);
206
+ }
207
+
208
+ .shadow-moloo-lg {
209
+ box-shadow: var(--moloo-shadow-lg);
210
+ }
211
+
212
+ .shadow-moloo-hover {
213
+ box-shadow: var(--moloo-shadow-hover);
214
+ }
215
+
216
+ .shadow-moloo-active {
217
+ box-shadow: var(--moloo-shadow-active);
218
+ }
219
+
220
+ .shadow-moloo-inset {
221
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.25);
222
+ }
223
+
224
+ .moloo-surface {
225
+ border-radius: var(--radius);
226
+ border: 2px solid var(--border);
227
+ background: var(--popover);
228
+ box-shadow: var(--moloo-shadow-lg);
229
+ }
230
+
231
+ .moloo-field {
232
+ border-radius: var(--radius);
233
+ border: 2px solid var(--border);
234
+ background: var(--field-bg);
235
+ }
236
+
237
+ .moloo-number-input,
238
+ .moloo-field[type="number"] {
239
+ -moz-appearance: textfield;
240
+ appearance: textfield;
241
+ }
242
+
243
+ .moloo-number-input::-webkit-outer-spin-button,
244
+ .moloo-number-input::-webkit-inner-spin-button,
245
+ .moloo-field[type="number"]::-webkit-outer-spin-button,
246
+ .moloo-field[type="number"]::-webkit-inner-spin-button {
247
+ margin: 0;
248
+ -webkit-appearance: none;
249
+ appearance: none;
250
+ }
251
+
252
+ .moloo-focus {
253
+ outline-color: var(--destructive);
254
+ }
255
+
256
+ .moloo-focus:focus-visible {
257
+ outline: 2px solid var(--destructive);
258
+ outline-offset: 2px;
259
+ }
260
+
261
+ .moloo-focus-within {
262
+ outline-color: var(--destructive);
263
+ }
264
+
265
+ .moloo-focus-within:focus-within {
266
+ outline: 2px solid var(--destructive);
267
+ outline-offset: 2px;
268
+ }
269
+
270
+ .moloo-focus-within-inset {
271
+ outline-color: var(--destructive);
272
+ }
273
+
274
+ .moloo-focus-within-inset:focus-within {
275
+ outline: 2px solid var(--destructive);
276
+ outline-offset: -2px;
277
+ }
278
+
279
+ .group\/input-group [data-slot="input-group-control"].moloo-field,
280
+ .group\/input-group [data-slot="input"].moloo-field,
281
+ .group\/input-group [data-slot="textarea"].moloo-field {
282
+ border: 0;
283
+ border-radius: 0;
284
+ background: transparent;
285
+ box-shadow: none;
286
+ }
287
+
288
+ .group\/input-group [data-slot="input-stepper"].moloo-field {
289
+ border: 0;
290
+ border-radius: 0;
291
+ background: transparent;
292
+ box-shadow: none;
293
+ padding: 0;
294
+ opacity: 1;
295
+ }
296
+
297
+ .group\/input-group .moloo-focus:focus-visible {
298
+ outline: none;
299
+ }
300
+
301
+ .group\/input-group [data-slot="input-stepper"].moloo-focus-within:focus-within {
302
+ outline: none;
303
+ }
304
+
305
+ .group\/input-group .moloo-press:hover:not(:disabled):not([aria-disabled="true"]),
306
+ .group\/input-group .moloo-press:active:not(:disabled):not([aria-disabled="true"]) {
307
+ transform: none;
308
+ box-shadow: none;
309
+ }
310
+
311
+ [data-slot="button-group"] .moloo-press:hover:not(:disabled):not([aria-disabled="true"]),
312
+ [data-slot="button-group"] .moloo-press:active:not(:disabled):not([aria-disabled="true"]) {
313
+ transform: none;
314
+ box-shadow: var(--moloo-shadow);
315
+ }
316
+
317
+ .moloo-press {
318
+ transition:
319
+ transform 160ms cubic-bezier(0.2, 0.8, 0.2, 1),
320
+ box-shadow 160ms cubic-bezier(0.2, 0.8, 0.2, 1),
321
+ background 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
322
+ }
323
+
324
+ .moloo-press:hover:not(:disabled):not([aria-disabled="true"]) {
325
+ transform: translate(-1px, -1px);
326
+ box-shadow: var(--moloo-shadow-hover);
327
+ }
328
+
329
+ .moloo-press:active:not(:disabled):not([aria-disabled="true"]) {
330
+ transform: translate(1px, 1px);
331
+ box-shadow: var(--moloo-shadow-active);
332
+ }
333
+
334
+ .sticker {
335
+ display: inline-flex;
336
+ align-items: center;
337
+ font-family: var(--font-display);
338
+ font-weight: 800;
339
+ letter-spacing: -0.03em;
340
+ background: var(--accent);
341
+ color: var(--accent-foreground);
342
+ padding: 0.22rem 0.5rem;
343
+ border-radius: 0.25rem;
344
+ transform: rotate(-2deg);
345
+ box-shadow: var(--moloo-shadow);
346
+ }
347
+
348
+ .bar-indeterminate {
349
+ animation: indeterminate 1.1s ease-in-out infinite;
350
+ }
351
+ }
352
+
353
+ @keyframes indeterminate {
354
+ 0% {
355
+ transform: translateX(-100%);
356
+ }
357
+ 100% {
358
+ transform: translateX(500%);
359
+ }
360
+ }