@genomicx/ui 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.
@@ -0,0 +1,6 @@
1
+ interface AppFooterProps {
2
+ appName?: string;
3
+ onReportBug?: () => void;
4
+ }
5
+ export declare function AppFooter({ appName, onReportBug }: AppFooterProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,8 @@
1
+ import { ReactNode } from 'react';
2
+ import { NavBarProps } from './NavBar';
3
+ interface AppShellProps extends NavBarProps {
4
+ children: ReactNode;
5
+ onReportBug?: () => void;
6
+ }
7
+ export declare function AppShell({ children, onReportBug, ...navProps }: AppShellProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,8 @@
1
+ import { ProgressUpdate } from '../types/progress';
2
+ interface LogConsoleProps {
3
+ logs: string[];
4
+ progress?: ProgressUpdate;
5
+ title?: string;
6
+ }
7
+ export declare function LogConsole({ logs, progress, title }: LogConsoleProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,11 @@
1
+ import { ReactNode } from 'react';
2
+ export interface NavBarProps {
3
+ appName: string;
4
+ appSubtitle?: string;
5
+ version?: string;
6
+ /** Extra items in the desktop nav (right side, before theme toggle) */
7
+ actions?: ReactNode;
8
+ /** Extra items in the mobile dropdown */
9
+ mobileActions?: ReactNode;
10
+ }
11
+ export declare function NavBar({ appName, appSubtitle, version, actions, mobileActions }: NavBarProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,3 @@
1
+ export declare function ThemeToggle({ disabled }: {
2
+ disabled?: boolean;
3
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ export { ThemeToggle } from './components/ThemeToggle';
2
+ export { NavBar } from './components/NavBar';
3
+ export type { NavBarProps } from './components/NavBar';
4
+ export { AppFooter } from './components/AppFooter';
5
+ export { AppShell } from './components/AppShell';
6
+ export { LogConsole } from './components/LogConsole';
7
+ export { loadWasmModule, createModuleInstance } from './wasm/loader';
8
+ export type { EmscriptenModule, WasmModuleFactory } from './wasm/types';
9
+ export { downloadBlob, downloadText, downloadBuffer } from './utils/download';
10
+ export type { ProgressUpdate } from './types/progress';
package/dist/index.js ADDED
@@ -0,0 +1,233 @@
1
+ import { jsxs as o, jsx as e } from "react/jsx-runtime";
2
+ import { useState as h, useRef as f } from "react";
3
+ import { Link as m } from "react-router-dom";
4
+ import u from "react-hot-toast";
5
+ function p({ disabled: t = !1 }) {
6
+ const [r, a] = h(
7
+ () => document.documentElement.getAttribute("data-theme") || "light"
8
+ ), l = (n) => {
9
+ a(n), document.documentElement.setAttribute("data-theme", n), localStorage.setItem("gx-theme", n);
10
+ };
11
+ return /* @__PURE__ */ o(
12
+ "div",
13
+ {
14
+ className: `flex items-center rounded-full border overflow-hidden text-xs font-medium ${t ? "opacity-40 pointer-events-none" : ""}`,
15
+ style: { borderColor: "var(--gx-border)" },
16
+ title: t ? "Theme switching disabled" : void 0,
17
+ children: [
18
+ /* @__PURE__ */ e(
19
+ "button",
20
+ {
21
+ onClick: () => !t && l("dark"),
22
+ className: "px-3 py-1.5 transition-colors",
23
+ style: r === "dark" ? { background: "var(--gx-accent)", color: "var(--gx-text-inverted)" } : { color: "var(--gx-text-muted)" },
24
+ "aria-label": "Dark theme",
25
+ disabled: t,
26
+ children: /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ e("path", { d: "M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" }) })
27
+ }
28
+ ),
29
+ /* @__PURE__ */ e(
30
+ "button",
31
+ {
32
+ onClick: () => !t && l("light"),
33
+ className: "px-3 py-1.5 transition-colors",
34
+ style: r === "light" ? { background: "var(--gx-accent)", color: "var(--gx-text-inverted)" } : { color: "var(--gx-text-muted)" },
35
+ "aria-label": "Light theme",
36
+ disabled: t,
37
+ children: /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ e("path", { fillRule: "evenodd", d: "M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z", clipRule: "evenodd" }) })
38
+ }
39
+ )
40
+ ]
41
+ }
42
+ );
43
+ }
44
+ function b({ appName: t, appSubtitle: r, version: a, actions: l, mobileActions: n }) {
45
+ const [s, c] = h(!1);
46
+ return /* @__PURE__ */ o("nav", { className: "sticky top-0 z-40", style: { background: "var(--gx-nav-bg)", backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)", borderBottom: "1px solid var(--gx-border)" }, children: [
47
+ /* @__PURE__ */ e("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ o("div", { className: "flex items-center justify-between h-[60px]", children: [
48
+ /* @__PURE__ */ o(m, { to: "/", className: "flex items-center gap-3 hover:opacity-90 transition-opacity", children: [
49
+ /* @__PURE__ */ o("svg", { className: "w-7 h-7", viewBox: "0 0 24 24", fill: "none", stroke: "var(--gx-accent)", strokeWidth: "2", children: [
50
+ /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "10" }),
51
+ /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "6" }),
52
+ /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "2" })
53
+ ] }),
54
+ /* @__PURE__ */ o("div", { children: [
55
+ /* @__PURE__ */ o("h1", { className: "text-lg font-bold", style: { color: "var(--gx-text)" }, children: [
56
+ t,
57
+ a && /* @__PURE__ */ o("span", { className: "text-xs font-normal ml-1", style: { color: "var(--gx-text-muted)" }, children: [
58
+ "v",
59
+ a
60
+ ] })
61
+ ] }),
62
+ r && /* @__PURE__ */ e("p", { className: "text-xs", style: { color: "var(--gx-text-muted)" }, children: r })
63
+ ] })
64
+ ] }),
65
+ /* @__PURE__ */ o("div", { className: "hidden md:flex items-center gap-6", children: [
66
+ l,
67
+ /* @__PURE__ */ e(m, { to: "/about", className: "text-sm font-medium transition-colors", style: { color: "var(--gx-text-muted)" }, children: "About" }),
68
+ /* @__PURE__ */ o(
69
+ "a",
70
+ {
71
+ href: "https://github.com/happykhan",
72
+ target: "_blank",
73
+ rel: "noopener noreferrer",
74
+ className: "text-sm font-medium transition-colors inline-flex items-center gap-1",
75
+ style: { color: "var(--gx-text-muted)" },
76
+ children: [
77
+ "GitHub",
78
+ /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" }) })
79
+ ]
80
+ }
81
+ ),
82
+ /* @__PURE__ */ e(p, {})
83
+ ] }),
84
+ /* @__PURE__ */ o("div", { className: "flex md:hidden items-center gap-3", children: [
85
+ /* @__PURE__ */ e(p, {}),
86
+ /* @__PURE__ */ e(
87
+ "button",
88
+ {
89
+ onClick: () => c(!s),
90
+ className: "p-2 rounded",
91
+ style: { color: "var(--gx-text-muted)" },
92
+ "aria-label": "Toggle menu",
93
+ children: s ? /* @__PURE__ */ e("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) : /* @__PURE__ */ e("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) })
94
+ }
95
+ )
96
+ ] })
97
+ ] }) }),
98
+ s && /* @__PURE__ */ o("div", { className: "md:hidden px-4 pb-4 space-y-2", style: { borderTop: "1px solid var(--gx-border)", background: "var(--gx-nav-bg)" }, children: [
99
+ n,
100
+ /* @__PURE__ */ e(m, { to: "/about", onClick: () => c(!1), className: "block text-sm py-2 transition-colors", style: { color: "var(--gx-text-muted)" }, children: "About" }),
101
+ /* @__PURE__ */ o(
102
+ "a",
103
+ {
104
+ href: "https://github.com/happykhan",
105
+ target: "_blank",
106
+ rel: "noopener noreferrer",
107
+ className: "inline-flex items-center gap-1 text-sm py-2 transition-colors",
108
+ style: { color: "var(--gx-text-muted)" },
109
+ children: [
110
+ "GitHub",
111
+ /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" }) })
112
+ ]
113
+ }
114
+ )
115
+ ] })
116
+ ] });
117
+ }
118
+ function y({ appName: t = "GenomicX", onReportBug: r }) {
119
+ return /* @__PURE__ */ e("footer", { className: "mt-auto py-6", style: { borderTop: "1px solid var(--gx-border)", background: "var(--gx-bg-alt)" }, children: /* @__PURE__ */ e("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ o("div", { className: "flex flex-col md:flex-row justify-between items-center", children: [
120
+ /* @__PURE__ */ o("div", { className: "text-sm mb-4 md:mb-0", style: { color: "var(--gx-text-muted)" }, children: [
121
+ /* @__PURE__ */ o("p", { className: "font-semibold", style: { color: "var(--gx-text)" }, children: [
122
+ t,
123
+ " — Powered by BLAST & WebAssembly"
124
+ ] }),
125
+ /* @__PURE__ */ e("p", { className: "mt-1", children: "All processing runs locally in your browser — no data leaves your computer" })
126
+ ] }),
127
+ /* @__PURE__ */ o("div", { className: "flex gap-6 text-sm", children: [
128
+ /* @__PURE__ */ e("a", { href: "https://genomicx.org", target: "_blank", rel: "noopener noreferrer", className: "transition-colors hover:text-[var(--gx-accent)]", style: { color: "var(--gx-text-muted)" }, children: "genomicx.org" }),
129
+ r && /* @__PURE__ */ e("button", { onClick: r, className: "transition-colors hover:text-[var(--gx-accent)]", style: { color: "var(--gx-text-muted)" }, children: "Report Bug" })
130
+ ] })
131
+ ] }) }) });
132
+ }
133
+ function C({ children: t, onReportBug: r, ...a }) {
134
+ return /* @__PURE__ */ o("div", { className: "min-h-screen flex flex-col", style: { background: "var(--gx-bg)" }, children: [
135
+ /* @__PURE__ */ e(b, { ...a }),
136
+ /* @__PURE__ */ e("main", { className: "flex-1", children: t }),
137
+ /* @__PURE__ */ e(y, { appName: a.appName, onReportBug: r })
138
+ ] });
139
+ }
140
+ function j({ logs: t, progress: r, title: a = "Console" }) {
141
+ const [l, n] = h(!0), s = f(null), c = () => {
142
+ navigator.clipboard.writeText(t.join(`
143
+ `)).then(() => {
144
+ u.success("Logs copied to clipboard!");
145
+ }).catch(() => {
146
+ u.error("Failed to copy logs");
147
+ });
148
+ }, i = r && r.step !== "idle" && r.step !== "Complete!";
149
+ return /* @__PURE__ */ o("div", { className: "card mt-6", children: [
150
+ i && /* @__PURE__ */ o("div", { className: "mb-4 pb-4", style: { borderBottom: "1px solid var(--gx-border)" }, children: [
151
+ /* @__PURE__ */ o("div", { className: "flex items-center justify-between mb-2", children: [
152
+ /* @__PURE__ */ e("span", { className: "text-sm font-medium", style: { color: "var(--gx-text)" }, children: r.step }),
153
+ /* @__PURE__ */ o("span", { className: "text-sm", style: { color: "var(--gx-text-muted)" }, children: [
154
+ r.percent,
155
+ "%"
156
+ ] })
157
+ ] }),
158
+ /* @__PURE__ */ e("div", { className: "progress-bg", children: /* @__PURE__ */ e("div", { className: "progress-bar", style: { width: `${r.percent}%` } }) }),
159
+ r.message && /* @__PURE__ */ e("div", { className: "mt-2 text-xs", style: { color: "var(--gx-text-muted)" }, children: r.message })
160
+ ] }),
161
+ /* @__PURE__ */ o("div", { className: "flex items-center justify-between mb-3", children: [
162
+ /* @__PURE__ */ o("div", { className: "flex items-center gap-2", children: [
163
+ /* @__PURE__ */ e("button", { onClick: () => n(!l), style: { color: "var(--gx-text-muted)" }, children: l ? "▼" : "▶" }),
164
+ /* @__PURE__ */ e("h3", { className: "font-semibold", style: { color: "var(--gx-text)" }, children: a }),
165
+ /* @__PURE__ */ o("span", { className: "text-xs", style: { color: "var(--gx-text-muted)" }, children: [
166
+ "(",
167
+ t.length,
168
+ " messages)"
169
+ ] })
170
+ ] }),
171
+ /* @__PURE__ */ o("button", { onClick: c, className: "btn-secondary text-xs px-3 py-1", disabled: t.length === 0, children: [
172
+ /* @__PURE__ */ e("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
173
+ "Copy"
174
+ ] })
175
+ ] }),
176
+ l && /* @__PURE__ */ e(
177
+ "div",
178
+ {
179
+ ref: s,
180
+ className: "font-mono text-xs p-4 rounded max-h-96 overflow-y-auto",
181
+ style: { background: "var(--gx-code-bg)", color: "var(--gx-accent)", border: "1px solid var(--gx-border)" },
182
+ children: t.length === 0 ? /* @__PURE__ */ e("div", { style: { color: "var(--gx-text-muted)" }, children: "No logs yet..." }) : t.map((d, g) => /* @__PURE__ */ e("div", { className: "mb-1 whitespace-pre-wrap break-all", children: d }, g))
183
+ }
184
+ )
185
+ ] });
186
+ }
187
+ const k = "https://static.genomicx.org/wasm", x = /* @__PURE__ */ new Map();
188
+ async function N(t, r = k) {
189
+ const a = `${r}/${t}`;
190
+ if (x.has(a)) return x.get(a);
191
+ const [l, n] = await Promise.all([
192
+ fetch(`${r}/${t}.js`),
193
+ fetch(`${r}/${t}.wasm`)
194
+ ]);
195
+ if (!l.ok) throw new Error(`Failed to fetch ${t}.js: ${l.status}`);
196
+ if (!n.ok) throw new Error(`Failed to fetch ${t}.wasm: ${n.status}`);
197
+ const [s, c] = await Promise.all([
198
+ l.text(),
199
+ n.arrayBuffer()
200
+ ]), d = { factory: new Function("Module", s + "; return Module;")({}), wasmBinary: c };
201
+ return x.set(a, d), d;
202
+ }
203
+ async function A(t, r) {
204
+ const { factory: a, wasmBinary: l } = await N(t, r), n = [], s = [], c = await a({
205
+ wasmBinary: l.slice(0),
206
+ print: (i) => n.push(i),
207
+ printErr: (i) => s.push(i),
208
+ noInitialRun: !0
209
+ });
210
+ return c._stdout = n, c._stderr = s, c;
211
+ }
212
+ function v(t, r) {
213
+ const a = URL.createObjectURL(t), l = document.createElement("a");
214
+ l.href = a, l.download = r, l.click(), URL.revokeObjectURL(a);
215
+ }
216
+ function T(t, r, a = "text/plain") {
217
+ v(new Blob([t], { type: a }), r);
218
+ }
219
+ function z(t, r) {
220
+ v(new Blob([t]), r);
221
+ }
222
+ export {
223
+ y as AppFooter,
224
+ C as AppShell,
225
+ j as LogConsole,
226
+ b as NavBar,
227
+ p as ThemeToggle,
228
+ A as createModuleInstance,
229
+ v as downloadBlob,
230
+ z as downloadBuffer,
231
+ T as downloadText,
232
+ N as loadWasmModule
233
+ };
@@ -0,0 +1,5 @@
1
+ export interface ProgressUpdate {
2
+ step: string;
3
+ percent: number;
4
+ message?: string;
5
+ }
@@ -0,0 +1,6 @@
1
+ /** Trigger a browser file download from a Blob or string. */
2
+ export declare function downloadBlob(blob: Blob, filename: string): void;
3
+ /** Trigger a browser file download from a string. */
4
+ export declare function downloadText(text: string, filename: string, mimeType?: string): void;
5
+ /** Trigger a browser file download from an ArrayBuffer. */
6
+ export declare function downloadBuffer(buffer: ArrayBuffer, filename: string): void;
@@ -0,0 +1,15 @@
1
+ import { EmscriptenModule, WasmModuleFactory } from './types';
2
+ interface CachedModule {
3
+ factory: WasmModuleFactory;
4
+ wasmBinary: ArrayBuffer;
5
+ }
6
+ /**
7
+ * Load and cache an Emscripten WASM module by name.
8
+ * Fetches <name>.js and <name>.wasm from static.genomicx.org.
9
+ */
10
+ export declare function loadWasmModule(name: string, baseUrl?: string): Promise<CachedModule>;
11
+ /**
12
+ * Create a fresh Emscripten module instance (captures its own stdout/stderr).
13
+ */
14
+ export declare function createModuleInstance(name: string, baseUrl?: string): Promise<EmscriptenModule>;
15
+ export {};
@@ -0,0 +1,18 @@
1
+ export interface EmscriptenModule {
2
+ callMain: (args: string[]) => void;
3
+ FS: {
4
+ writeFile: (path: string, data: string | Uint8Array) => void;
5
+ readFile: (path: string) => Uint8Array;
6
+ unlink: (path: string) => void;
7
+ };
8
+ _stdout: string[];
9
+ _stderr: string[];
10
+ }
11
+ export interface WasmModuleFactory {
12
+ (options: {
13
+ wasmBinary: ArrayBuffer;
14
+ print: (text: string) => void;
15
+ printErr: (text: string) => void;
16
+ noInitialRun: true;
17
+ }): Promise<EmscriptenModule>;
18
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@genomicx/ui",
3
+ "version": "0.1.0",
4
+ "description": "Shared UI components, styles, and WASM loader for GenomicX tools",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./styles/tokens.css": "./src/styles/tokens.css",
15
+ "./styles/components.css": "./src/styles/components.css"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src/styles"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc && vite build",
23
+ "dev": "vite build --watch"
24
+ },
25
+ "peerDependencies": {
26
+ "react": "^18.0.0",
27
+ "react-dom": "^18.0.0",
28
+ "react-router-dom": "^6.0.0 || ^7.0.0",
29
+ "react-hot-toast": "^2.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/react": "^18.3.0",
33
+ "@types/react-dom": "^18.3.0",
34
+ "@vitejs/plugin-react": "^4.0.0",
35
+ "react": "^18.3.0",
36
+ "react-dom": "^18.3.0",
37
+ "react-router-dom": "^7.0.0",
38
+ "react-hot-toast": "^2.4.0",
39
+ "typescript": "^5.5.0",
40
+ "vite": "^6.0.0",
41
+ "vite-plugin-dts": "^4.0.0"
42
+ }
43
+ }
@@ -0,0 +1,125 @@
1
+ /* GenomicX Tailwind component classes — import after @tailwind directives */
2
+
3
+ @layer base {
4
+ html {
5
+ scroll-behavior: smooth;
6
+ -webkit-font-smoothing: antialiased;
7
+ -moz-osx-font-smoothing: grayscale;
8
+ }
9
+
10
+ body {
11
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
12
+ background-color: var(--gx-bg);
13
+ color: var(--gx-text);
14
+ line-height: 1.7;
15
+ transition: background-color var(--gx-transition), color var(--gx-transition);
16
+ margin: 0;
17
+ }
18
+
19
+ * {
20
+ transition: background-color var(--gx-transition), border-color var(--gx-transition), color var(--gx-transition);
21
+ }
22
+
23
+ code {
24
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
25
+ }
26
+
27
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
28
+ ::-webkit-scrollbar-track { background: var(--gx-bg); }
29
+ ::-webkit-scrollbar-thumb { background: var(--gx-border); border-radius: 999px; }
30
+ ::-webkit-scrollbar-thumb:hover { background: var(--gx-text-muted); }
31
+ }
32
+
33
+ @layer components {
34
+ .card {
35
+ background: var(--gx-bg-alt);
36
+ border: 1px solid var(--gx-border);
37
+ border-radius: var(--gx-radius-lg);
38
+ padding: 1.5rem;
39
+ transition: border-color var(--gx-transition);
40
+ }
41
+
42
+ .card:hover {
43
+ border-color: var(--gx-accent);
44
+ }
45
+
46
+ .btn-primary {
47
+ display: inline-flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ gap: 0.5rem;
51
+ background: var(--gx-accent);
52
+ color: var(--gx-text-inverted);
53
+ font-weight: 600;
54
+ font-size: 0.875rem;
55
+ padding: 0.7rem 1.4rem;
56
+ border-radius: 6px;
57
+ border: none;
58
+ cursor: pointer;
59
+ transition: all var(--gx-transition);
60
+ }
61
+
62
+ .btn-primary:hover { background: var(--gx-accent-hover); }
63
+ .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
64
+
65
+ .btn-secondary {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ gap: 0.5rem;
70
+ background: transparent;
71
+ color: var(--gx-text);
72
+ font-weight: 600;
73
+ font-size: 0.875rem;
74
+ padding: 0.7rem 1.4rem;
75
+ border-radius: 6px;
76
+ border: 1px solid var(--gx-border);
77
+ cursor: pointer;
78
+ transition: all var(--gx-transition);
79
+ }
80
+
81
+ .btn-secondary:hover { border-color: var(--gx-accent); color: var(--gx-accent); }
82
+
83
+ .input-field {
84
+ background: var(--gx-bg);
85
+ border: 1px solid var(--gx-border);
86
+ color: var(--gx-text);
87
+ border-radius: 6px;
88
+ padding: 0.5rem 0.75rem;
89
+ font-size: 0.875rem;
90
+ transition: border-color var(--gx-transition);
91
+ outline: none;
92
+ }
93
+
94
+ .input-field:focus { border-color: var(--gx-accent); }
95
+
96
+ .label {
97
+ display: block;
98
+ font-size: 0.8125rem;
99
+ font-weight: 500;
100
+ color: var(--gx-text);
101
+ margin-bottom: 0.5rem;
102
+ }
103
+
104
+ .section-title {
105
+ font-size: 1.25rem;
106
+ font-weight: 700;
107
+ color: var(--gx-text);
108
+ margin-bottom: 1rem;
109
+ letter-spacing: -0.01em;
110
+ }
111
+
112
+ .progress-bg {
113
+ background: var(--gx-bg);
114
+ height: 6px;
115
+ border-radius: 999px;
116
+ overflow: hidden;
117
+ }
118
+
119
+ .progress-bar {
120
+ background: var(--gx-accent);
121
+ height: 6px;
122
+ border-radius: 999px;
123
+ transition: width 0.3s ease;
124
+ }
125
+ }
@@ -0,0 +1,44 @@
1
+ /* GenomicX Design Tokens */
2
+ :root {
3
+ --gx-bg: #f8fafc;
4
+ --gx-bg-alt: #f1f5f9;
5
+ --gx-surface: #ffffff;
6
+ --gx-surface-hover: #f1f5f9;
7
+ --gx-bg-elevated: #ffffff;
8
+ --gx-text: #0f172a;
9
+ --gx-text-muted: #64748b;
10
+ --gx-text-bright: #0f172a;
11
+ --gx-text-inverted: #ffffff;
12
+ --gx-border: #e2e8f0;
13
+ --gx-accent: #0d9488;
14
+ --gx-accent-hover: #0f766e;
15
+ --gx-indigo: #6366F1;
16
+ --gx-success: #0d9488;
17
+ --gx-warning: #F59E0B;
18
+ --gx-error: #EF4444;
19
+ --gx-nav-bg: rgba(255, 255, 255, 0.9);
20
+ --gx-code-bg: #f1f5f9;
21
+ --gx-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
22
+ --gx-transition: 0.2s ease;
23
+ --gx-radius: 8px;
24
+ --gx-radius-lg: 12px;
25
+ }
26
+
27
+ [data-theme="dark"] {
28
+ --gx-bg: #0f172a;
29
+ --gx-bg-alt: #1e293b;
30
+ --gx-surface: #1e293b;
31
+ --gx-surface-hover: #334155;
32
+ --gx-bg-elevated: #1e293b;
33
+ --gx-text: #f1f5f9;
34
+ --gx-text-muted: #94a3b8;
35
+ --gx-text-bright: #f1f5f9;
36
+ --gx-text-inverted: #0f172a;
37
+ --gx-border: #334155;
38
+ --gx-accent: #2dd4bf;
39
+ --gx-accent-hover: #14b8a6;
40
+ --gx-indigo: #818cf8;
41
+ --gx-nav-bg: rgba(15, 23, 42, 0.9);
42
+ --gx-code-bg: #0f172a;
43
+ --gx-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
44
+ }