@genomicx/ui 0.1.0 → 0.3.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.
- package/README.md +285 -0
- package/dist/index.js +129 -180
- package/package.json +3 -3
- package/src/styles/components.css +399 -124
package/README.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# @genomicx/ui
|
|
2
|
+
|
|
3
|
+
Shared UI components, design tokens, WASM loader, and utilities for [GenomicX](https://genomicx.org) browser-based bioinformatics tools.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @genomicx/ui
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependencies (install separately):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install react react-dom react-router-dom react-hot-toast
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
Import the CSS in your app's global stylesheet, **after** your Tailwind directives:
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
@import "tailwindcss";
|
|
23
|
+
@import '@genomicx/ui/styles/tokens.css';
|
|
24
|
+
@import '@genomicx/ui/styles/components.css';
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Init the theme on load (prevents flash of wrong theme):
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const saved = (localStorage.getItem('gx-theme') as 'light' | 'dark') || 'dark'
|
|
32
|
+
document.documentElement.setAttribute('data-theme', saved)
|
|
33
|
+
}, [])
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Components
|
|
37
|
+
|
|
38
|
+
### `NavBar`
|
|
39
|
+
|
|
40
|
+
Sticky top navigation with the GenomicX rings logo, theme toggle, and an About link.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { NavBar } from '@genomicx/ui'
|
|
44
|
+
|
|
45
|
+
<NavBar
|
|
46
|
+
appName="MYAPP"
|
|
47
|
+
appSubtitle="What it does"
|
|
48
|
+
version="1.2.0"
|
|
49
|
+
/>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Props**
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Default | Description |
|
|
55
|
+
|------|------|---------|-------------|
|
|
56
|
+
| `appName` | `string` | — | App name shown in header (uppercase recommended) |
|
|
57
|
+
| `appSubtitle` | `string` | — | Subtitle shown below app name |
|
|
58
|
+
| `version` | `string` | — | Version shown as `v1.0.0` badge |
|
|
59
|
+
| `actions` | `ReactNode` | — | Extra items in the desktop nav (right side) |
|
|
60
|
+
| `mobileActions` | `ReactNode` | — | Extra items in the mobile dropdown |
|
|
61
|
+
|
|
62
|
+
Tool-specific nav buttons (e.g. Save/Load Session) go in `actions`:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<NavBar
|
|
66
|
+
appName="BRIGX"
|
|
67
|
+
appSubtitle="Browser-based Ring Image Generator"
|
|
68
|
+
version={APP_VERSION}
|
|
69
|
+
actions={
|
|
70
|
+
<>
|
|
71
|
+
<button onClick={handleSave}>Save Session</button>
|
|
72
|
+
<label>Load Session<input type="file" onChange={handleLoad} /></label>
|
|
73
|
+
</>
|
|
74
|
+
}
|
|
75
|
+
/>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### `AppFooter`
|
|
81
|
+
|
|
82
|
+
Standard footer with GenomicX branding and an optional bug report link.
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { AppFooter } from '@genomicx/ui'
|
|
86
|
+
|
|
87
|
+
<AppFooter appName="MYAPP" />
|
|
88
|
+
<AppFooter appName="MYAPP" onReportBug={() => setShowBugReport(true)} />
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### `LogConsole`
|
|
94
|
+
|
|
95
|
+
Collapsible debug console for displaying WASM stderr/stdout.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { LogConsole } from '@genomicx/ui'
|
|
99
|
+
|
|
100
|
+
<LogConsole logs={logLines} />
|
|
101
|
+
<LogConsole logs={logLines} progress="Running BLAST..." title="Debug Console" />
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Props**
|
|
105
|
+
|
|
106
|
+
| Prop | Type | Description |
|
|
107
|
+
|------|------|-------------|
|
|
108
|
+
| `logs` | `string[]` | Log lines to display |
|
|
109
|
+
| `progress` | `string` | Optional current progress message shown at top |
|
|
110
|
+
| `title` | `string` | Console title (default: `"Console"`) |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### `ThemeToggle`
|
|
115
|
+
|
|
116
|
+
Pill-style dark/light theme switcher. Already included in `NavBar` — use standalone only if building a custom nav.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { ThemeToggle } from '@genomicx/ui'
|
|
120
|
+
|
|
121
|
+
<ThemeToggle />
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### `AppShell`
|
|
127
|
+
|
|
128
|
+
Full-page layout wrapper (nav + main + footer).
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { AppShell } from '@genomicx/ui'
|
|
132
|
+
|
|
133
|
+
<AppShell appName="MYAPP" appSubtitle="What it does" version="0.1.0">
|
|
134
|
+
<YourContent />
|
|
135
|
+
</AppShell>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## WASM Loader
|
|
141
|
+
|
|
142
|
+
Canonical Emscripten loader for GenomicX WASM binaries hosted on `static.genomicx.org`.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { createModuleInstance } from '@genomicx/ui'
|
|
146
|
+
|
|
147
|
+
// Loads mash.js + mash.wasm from static.genomicx.org/wasm/
|
|
148
|
+
const mod = await createModuleInstance('mash')
|
|
149
|
+
|
|
150
|
+
mod.FS.writeFile('/query.fa', fastaBytes)
|
|
151
|
+
mod.callMain(['dist', '/db.msh', '/query.fa'])
|
|
152
|
+
const output = mod._stdout.join('\n')
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Available binaries: `mash`, `blastall`, `formatdb`
|
|
156
|
+
|
|
157
|
+
**Low-level API** (custom base URL or caching control):
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { loadWasmModule } from '@genomicx/ui'
|
|
161
|
+
|
|
162
|
+
const { factory, wasmBinary } = await loadWasmModule('blastall', 'https://my-cdn.example.com/wasm')
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Utilities
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { downloadBlob, downloadText, downloadBuffer } from '@genomicx/ui'
|
|
171
|
+
|
|
172
|
+
downloadText('results.tsv', tsvString)
|
|
173
|
+
downloadBuffer('output.fa', uint8Array, 'text/plain')
|
|
174
|
+
downloadBlob('report.json', new Blob([json], { type: 'application/json' }))
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Design Tokens
|
|
180
|
+
|
|
181
|
+
All tokens are CSS custom properties on `:root` / `[data-theme]`. Key tokens:
|
|
182
|
+
|
|
183
|
+
| Token | Light | Dark | Usage |
|
|
184
|
+
|-------|-------|------|-------|
|
|
185
|
+
| `--gx-accent` | `#0d9488` | `#2dd4bf` | Primary accent (teal) |
|
|
186
|
+
| `--gx-bg` | `#f8fafc` | `#0d1117` | Page background |
|
|
187
|
+
| `--gx-bg-alt` | `#fff` | `#161b22` | Card/panel background |
|
|
188
|
+
| `--gx-text` | `#0f172a` | `#e6edf3` | Body text |
|
|
189
|
+
| `--gx-text-muted` | `#64748b` | `#8b949e` | Secondary text |
|
|
190
|
+
| `--gx-border` | `#e2e8f0` | `#30363d` | Borders |
|
|
191
|
+
| `--gx-gradient` | — | — | Accent gradient (button backgrounds) |
|
|
192
|
+
| `--gx-font-sans` | — | — | `Inter, system-ui, sans-serif` |
|
|
193
|
+
| `--gx-font-mono` | — | — | `JetBrains Mono, monospace` |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Typical app scaffold
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
src/
|
|
201
|
+
main.tsx ← BrowserRouter + Toaster
|
|
202
|
+
App.tsx ← NavBar + Routes + AppFooter
|
|
203
|
+
index.css ← Tailwind + @genomicx/ui tokens + components
|
|
204
|
+
lib/version.ts ← export const APP_VERSION = '0.1.0'
|
|
205
|
+
pages/About.tsx
|
|
206
|
+
components/
|
|
207
|
+
[tool]/
|
|
208
|
+
pipeline.ts
|
|
209
|
+
types.ts
|
|
210
|
+
databases.ts
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`index.css`:
|
|
214
|
+
```css
|
|
215
|
+
@import '@genomicx/ui/styles/tokens.css';
|
|
216
|
+
@import '@genomicx/ui/styles/components.css';
|
|
217
|
+
@import "tailwindcss";
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`App.tsx`:
|
|
221
|
+
```tsx
|
|
222
|
+
import { NavBar, AppFooter, LogConsole } from '@genomicx/ui'
|
|
223
|
+
import { APP_VERSION } from './lib/version'
|
|
224
|
+
|
|
225
|
+
function App() {
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
const theme = localStorage.getItem('gx-theme') as 'light' | 'dark' || 'dark'
|
|
228
|
+
document.documentElement.setAttribute('data-theme', theme)
|
|
229
|
+
}, [])
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<div className="app">
|
|
233
|
+
<NavBar appName="MYAPP" appSubtitle="What it does" version={APP_VERSION} />
|
|
234
|
+
<main className="app-main">
|
|
235
|
+
<Routes>
|
|
236
|
+
<Route path="/" element={<AnalysisPage />} />
|
|
237
|
+
<Route path="/about" element={<About />} />
|
|
238
|
+
</Routes>
|
|
239
|
+
</main>
|
|
240
|
+
<AppFooter appName="MYAPP" />
|
|
241
|
+
</div>
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Tools using this package
|
|
249
|
+
|
|
250
|
+
| Tool | Repo |
|
|
251
|
+
|------|------|
|
|
252
|
+
| BRIGx | [happykhan/brigx](https://github.com/happykhan/brigx) |
|
|
253
|
+
| MashX | [genomicx/mashx](https://github.com/genomicx/mashx) |
|
|
254
|
+
| Genetrax | [genomicx/genetrax](https://github.com/genomicx/genetrax) |
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Development
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
git clone https://github.com/genomicx/ui
|
|
262
|
+
cd ui
|
|
263
|
+
npm install
|
|
264
|
+
npm run build # compile to dist/
|
|
265
|
+
npm run dev # watch mode
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
To test changes locally in a consuming app before publishing:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# In this repo
|
|
272
|
+
npm link
|
|
273
|
+
|
|
274
|
+
# In the consuming app
|
|
275
|
+
npm link @genomicx/ui
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Publishing
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npm run build
|
|
282
|
+
npm publish --access public
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Requires membership in the `@genomicx` npm org and an Automation token in `~/.npmrc`.
|
package/dist/index.js
CHANGED
|
@@ -1,233 +1,182 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useState as
|
|
3
|
-
import { Link as
|
|
4
|
-
import
|
|
5
|
-
function
|
|
6
|
-
const [
|
|
7
|
-
() => document.documentElement.getAttribute("data-theme") || "
|
|
8
|
-
), l = (
|
|
9
|
-
|
|
1
|
+
import { jsxs as a, jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { useState as v, useRef as p } from "react";
|
|
3
|
+
import { Link as d } from "react-router-dom";
|
|
4
|
+
import g from "react-hot-toast";
|
|
5
|
+
function u({ disabled: n = !1 }) {
|
|
6
|
+
const [o, t] = v(
|
|
7
|
+
() => document.documentElement.getAttribute("data-theme") || "dark"
|
|
8
|
+
), l = (r) => {
|
|
9
|
+
t(r), document.documentElement.setAttribute("data-theme", r), localStorage.setItem("gx-theme", r);
|
|
10
10
|
};
|
|
11
|
-
return /* @__PURE__ */
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/* @__PURE__ */ e(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
);
|
|
11
|
+
return /* @__PURE__ */ a("div", { className: `gx-theme-toggle${n ? " disabled" : ""}`, title: n ? "Theme switching disabled" : void 0, children: [
|
|
12
|
+
/* @__PURE__ */ e(
|
|
13
|
+
"button",
|
|
14
|
+
{
|
|
15
|
+
onClick: () => !n && l("dark"),
|
|
16
|
+
className: `gx-theme-btn${o === "dark" ? " active" : ""}`,
|
|
17
|
+
"aria-label": "Dark theme",
|
|
18
|
+
disabled: n,
|
|
19
|
+
children: /* @__PURE__ */ e("svg", { className: "gx-theme-btn-icon", 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" }) })
|
|
20
|
+
}
|
|
21
|
+
),
|
|
22
|
+
/* @__PURE__ */ e(
|
|
23
|
+
"button",
|
|
24
|
+
{
|
|
25
|
+
onClick: () => !n && l("light"),
|
|
26
|
+
className: `gx-theme-btn${o === "light" ? " active" : ""}`,
|
|
27
|
+
"aria-label": "Light theme",
|
|
28
|
+
disabled: n,
|
|
29
|
+
children: /* @__PURE__ */ e("svg", { className: "gx-theme-btn-icon", 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" }) })
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
] });
|
|
43
33
|
}
|
|
44
|
-
function
|
|
45
|
-
const [
|
|
46
|
-
return /* @__PURE__ */
|
|
47
|
-
/* @__PURE__ */ e("div", { className: "
|
|
48
|
-
/* @__PURE__ */
|
|
49
|
-
/* @__PURE__ */
|
|
34
|
+
function f({ appName: n, appSubtitle: o, version: t, actions: l, mobileActions: r }) {
|
|
35
|
+
const [c, s] = v(!1);
|
|
36
|
+
return /* @__PURE__ */ a("nav", { className: "gx-nav", children: [
|
|
37
|
+
/* @__PURE__ */ e("div", { className: "gx-nav-inner", children: /* @__PURE__ */ a("div", { className: "gx-nav-row", children: [
|
|
38
|
+
/* @__PURE__ */ a(d, { to: "/", className: "gx-nav-logo", children: [
|
|
39
|
+
/* @__PURE__ */ a("svg", { className: "gx-nav-logo-icon", viewBox: "0 0 24 24", fill: "none", stroke: "var(--gx-accent)", strokeWidth: "2", children: [
|
|
50
40
|
/* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "10" }),
|
|
51
41
|
/* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "6" }),
|
|
52
42
|
/* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "2" })
|
|
53
43
|
] }),
|
|
54
|
-
/* @__PURE__ */
|
|
55
|
-
/* @__PURE__ */
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
/* @__PURE__ */ a("div", { children: [
|
|
45
|
+
/* @__PURE__ */ a("h1", { className: "gx-nav-logo-name", children: [
|
|
46
|
+
n,
|
|
47
|
+
t && /* @__PURE__ */ a("span", { className: "gx-nav-logo-version", children: [
|
|
58
48
|
"v",
|
|
59
|
-
|
|
49
|
+
t
|
|
60
50
|
] })
|
|
61
51
|
] }),
|
|
62
|
-
|
|
52
|
+
o && /* @__PURE__ */ e("p", { className: "gx-nav-logo-sub", children: o })
|
|
63
53
|
] })
|
|
64
54
|
] }),
|
|
65
|
-
/* @__PURE__ */
|
|
55
|
+
/* @__PURE__ */ a("div", { className: "gx-nav-desktop", children: [
|
|
66
56
|
l,
|
|
67
|
-
/* @__PURE__ */ e(
|
|
68
|
-
/* @__PURE__ */
|
|
69
|
-
"
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
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, {})
|
|
57
|
+
/* @__PURE__ */ e(d, { to: "/about", className: "gx-nav-link", children: "About" }),
|
|
58
|
+
/* @__PURE__ */ a("a", { href: "https://github.com/happykhan", target: "_blank", rel: "noopener noreferrer", className: "gx-nav-link", children: [
|
|
59
|
+
"GitHub",
|
|
60
|
+
/* @__PURE__ */ e("svg", { className: "gx-nav-link-icon", 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" }) })
|
|
61
|
+
] }),
|
|
62
|
+
/* @__PURE__ */ e(u, {})
|
|
83
63
|
] }),
|
|
84
|
-
/* @__PURE__ */
|
|
85
|
-
/* @__PURE__ */ e(
|
|
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
|
-
)
|
|
64
|
+
/* @__PURE__ */ a("div", { className: "gx-nav-mobile-toggle", children: [
|
|
65
|
+
/* @__PURE__ */ e(u, {}),
|
|
66
|
+
/* @__PURE__ */ e("button", { onClick: () => s(!c), className: "gx-nav-hamburger", "aria-label": "Toggle menu", children: c ? /* @__PURE__ */ e("svg", { className: "gx-nav-hamburger-icon", 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: "gx-nav-hamburger-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) }) })
|
|
96
67
|
] })
|
|
97
68
|
] }) }),
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
/* @__PURE__ */ e(
|
|
101
|
-
/* @__PURE__ */
|
|
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
|
-
)
|
|
69
|
+
c && /* @__PURE__ */ a("div", { className: "gx-nav-dropdown", children: [
|
|
70
|
+
r,
|
|
71
|
+
/* @__PURE__ */ e(d, { to: "/about", onClick: () => s(!1), className: "gx-nav-dropdown-link", children: "About" }),
|
|
72
|
+
/* @__PURE__ */ e("a", { href: "https://github.com/happykhan", target: "_blank", rel: "noopener noreferrer", className: "gx-nav-dropdown-link", children: "GitHub ↗" })
|
|
115
73
|
] })
|
|
116
74
|
] });
|
|
117
75
|
}
|
|
118
|
-
function
|
|
119
|
-
return /* @__PURE__ */ e("footer", { className: "
|
|
120
|
-
/* @__PURE__ */
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
-
|
|
123
|
-
" — Powered by
|
|
76
|
+
function N({ appName: n = "GenomicX", onReportBug: o }) {
|
|
77
|
+
return /* @__PURE__ */ e("footer", { className: "gx-footer", children: /* @__PURE__ */ e("div", { className: "gx-footer-inner", children: /* @__PURE__ */ a("div", { className: "gx-footer-content", children: [
|
|
78
|
+
/* @__PURE__ */ a("div", { className: "gx-footer-text", children: [
|
|
79
|
+
/* @__PURE__ */ a("p", { className: "gx-footer-text-title", children: [
|
|
80
|
+
n,
|
|
81
|
+
" — Powered by WebAssembly"
|
|
124
82
|
] }),
|
|
125
|
-
/* @__PURE__ */ e("p", { className: "
|
|
83
|
+
/* @__PURE__ */ e("p", { className: "gx-footer-text-sub", children: "All processing runs locally in your browser — no data leaves your computer" })
|
|
126
84
|
] }),
|
|
127
|
-
/* @__PURE__ */
|
|
128
|
-
/* @__PURE__ */ e("a", { href: "https://genomicx.org", target: "_blank", rel: "noopener noreferrer", className: "
|
|
129
|
-
|
|
85
|
+
/* @__PURE__ */ a("div", { className: "gx-footer-links", children: [
|
|
86
|
+
/* @__PURE__ */ e("a", { href: "https://genomicx.org", target: "_blank", rel: "noopener noreferrer", className: "gx-footer-link", children: "genomicx.org" }),
|
|
87
|
+
o && /* @__PURE__ */ e("button", { onClick: o, className: "gx-footer-link", children: "Report Bug" })
|
|
130
88
|
] })
|
|
131
89
|
] }) }) });
|
|
132
90
|
}
|
|
133
|
-
function
|
|
134
|
-
return /* @__PURE__ */
|
|
135
|
-
/* @__PURE__ */ e(
|
|
136
|
-
/* @__PURE__ */ e("main", {
|
|
137
|
-
/* @__PURE__ */ e(
|
|
91
|
+
function B({ children: n, onReportBug: o, ...t }) {
|
|
92
|
+
return /* @__PURE__ */ a("div", { style: { minHeight: "100vh", display: "flex", flexDirection: "column", background: "var(--gx-bg)" }, children: [
|
|
93
|
+
/* @__PURE__ */ e(f, { ...t }),
|
|
94
|
+
/* @__PURE__ */ e("main", { style: { flex: 1 }, children: n }),
|
|
95
|
+
/* @__PURE__ */ e(N, { appName: t.appName, onReportBug: o })
|
|
138
96
|
] });
|
|
139
97
|
}
|
|
140
|
-
function
|
|
141
|
-
const
|
|
142
|
-
navigator.clipboard.writeText(
|
|
98
|
+
function C({ logs: n, progress: o, title: t = "Console" }) {
|
|
99
|
+
const l = p(null), r = () => {
|
|
100
|
+
navigator.clipboard.writeText(n.join(`
|
|
143
101
|
`)).then(() => {
|
|
144
|
-
|
|
102
|
+
g.success("Logs copied to clipboard!");
|
|
145
103
|
}).catch(() => {
|
|
146
|
-
|
|
104
|
+
g.error("Failed to copy logs");
|
|
147
105
|
});
|
|
148
|
-
},
|
|
149
|
-
return /* @__PURE__ */
|
|
150
|
-
|
|
151
|
-
/* @__PURE__ */
|
|
152
|
-
/* @__PURE__ */ e("span", { className: "
|
|
153
|
-
/* @__PURE__ */
|
|
154
|
-
|
|
106
|
+
}, c = o && o.step !== "idle" && o.step !== "Complete!";
|
|
107
|
+
return /* @__PURE__ */ a("div", { className: "gx-console", children: [
|
|
108
|
+
c && /* @__PURE__ */ a("div", { className: "gx-console-progress", children: [
|
|
109
|
+
/* @__PURE__ */ a("div", { className: "gx-console-progress-row", children: [
|
|
110
|
+
/* @__PURE__ */ e("span", { className: "gx-console-progress-step", children: o.step }),
|
|
111
|
+
/* @__PURE__ */ a("span", { className: "gx-console-progress-pct", children: [
|
|
112
|
+
o.percent,
|
|
155
113
|
"%"
|
|
156
114
|
] })
|
|
157
115
|
] }),
|
|
158
|
-
/* @__PURE__ */ e("div", { className: "progress-bg", children: /* @__PURE__ */ e("div", { className: "progress-bar", style: { width: `${
|
|
159
|
-
|
|
116
|
+
/* @__PURE__ */ e("div", { className: "progress-bg", children: /* @__PURE__ */ e("div", { className: "progress-bar", style: { width: `${o.percent}%` } }) }),
|
|
117
|
+
o.message && /* @__PURE__ */ e("div", { className: "gx-console-progress-msg", children: o.message })
|
|
160
118
|
] }),
|
|
161
|
-
/* @__PURE__ */
|
|
162
|
-
/* @__PURE__ */
|
|
163
|
-
/* @__PURE__ */ e("
|
|
164
|
-
/* @__PURE__ */
|
|
165
|
-
/* @__PURE__ */ o("span", { className: "text-xs", style: { color: "var(--gx-text-muted)" }, children: [
|
|
119
|
+
/* @__PURE__ */ a("div", { className: "gx-console-header", children: [
|
|
120
|
+
/* @__PURE__ */ a("div", { className: "gx-console-title-row", children: [
|
|
121
|
+
/* @__PURE__ */ e("h3", { className: "gx-console-title", children: t }),
|
|
122
|
+
/* @__PURE__ */ a("span", { className: "gx-console-count", children: [
|
|
166
123
|
"(",
|
|
167
|
-
|
|
124
|
+
n.length,
|
|
168
125
|
" messages)"
|
|
169
126
|
] })
|
|
170
127
|
] }),
|
|
171
|
-
/* @__PURE__ */
|
|
172
|
-
/* @__PURE__ */ e("svg", { className: "
|
|
128
|
+
/* @__PURE__ */ a("button", { onClick: r, className: "gx-console-copy", disabled: n.length === 0, children: [
|
|
129
|
+
/* @__PURE__ */ e("svg", { className: "gx-console-copy-icon", 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
130
|
"Copy"
|
|
174
131
|
] })
|
|
175
132
|
] }),
|
|
176
|
-
l
|
|
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
|
-
)
|
|
133
|
+
/* @__PURE__ */ e("div", { ref: l, className: "gx-console-body", children: n.length === 0 ? /* @__PURE__ */ e("div", { className: "gx-console-empty", children: "No logs yet..." }) : n.map((s, i) => /* @__PURE__ */ e("div", { className: "gx-console-line", children: s }, i)) })
|
|
185
134
|
] });
|
|
186
135
|
}
|
|
187
|
-
const k = "https://static.genomicx.org/wasm",
|
|
188
|
-
async function
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
const [l,
|
|
192
|
-
fetch(`${
|
|
193
|
-
fetch(`${
|
|
136
|
+
const k = "https://static.genomicx.org/wasm", h = /* @__PURE__ */ new Map();
|
|
137
|
+
async function b(n, o = k) {
|
|
138
|
+
const t = `${o}/${n}`;
|
|
139
|
+
if (h.has(t)) return h.get(t);
|
|
140
|
+
const [l, r] = await Promise.all([
|
|
141
|
+
fetch(`${o}/${n}.js`),
|
|
142
|
+
fetch(`${o}/${n}.wasm`)
|
|
194
143
|
]);
|
|
195
|
-
if (!l.ok) throw new Error(`Failed to fetch ${
|
|
196
|
-
if (!
|
|
197
|
-
const [
|
|
144
|
+
if (!l.ok) throw new Error(`Failed to fetch ${n}.js: ${l.status}`);
|
|
145
|
+
if (!r.ok) throw new Error(`Failed to fetch ${n}.wasm: ${r.status}`);
|
|
146
|
+
const [c, s] = await Promise.all([
|
|
198
147
|
l.text(),
|
|
199
|
-
|
|
200
|
-
]),
|
|
201
|
-
return
|
|
148
|
+
r.arrayBuffer()
|
|
149
|
+
]), m = { factory: new Function("Module", c + "; return Module;")({}), wasmBinary: s };
|
|
150
|
+
return h.set(t, m), m;
|
|
202
151
|
}
|
|
203
|
-
async function
|
|
204
|
-
const { factory:
|
|
152
|
+
async function $(n, o) {
|
|
153
|
+
const { factory: t, wasmBinary: l } = await b(n, o), r = [], c = [], s = await t({
|
|
205
154
|
wasmBinary: l.slice(0),
|
|
206
|
-
print: (i) =>
|
|
207
|
-
printErr: (i) =>
|
|
155
|
+
print: (i) => r.push(i),
|
|
156
|
+
printErr: (i) => c.push(i),
|
|
208
157
|
noInitialRun: !0
|
|
209
158
|
});
|
|
210
|
-
return
|
|
159
|
+
return s._stdout = r, s._stderr = c, s;
|
|
211
160
|
}
|
|
212
|
-
function
|
|
213
|
-
const
|
|
214
|
-
l.href =
|
|
161
|
+
function x(n, o) {
|
|
162
|
+
const t = URL.createObjectURL(n), l = document.createElement("a");
|
|
163
|
+
l.href = t, l.download = o, l.click(), URL.revokeObjectURL(t);
|
|
215
164
|
}
|
|
216
|
-
function
|
|
217
|
-
|
|
165
|
+
function j(n, o, t = "text/plain") {
|
|
166
|
+
x(new Blob([n], { type: t }), o);
|
|
218
167
|
}
|
|
219
|
-
function
|
|
220
|
-
|
|
168
|
+
function A(n, o) {
|
|
169
|
+
x(new Blob([n]), o);
|
|
221
170
|
}
|
|
222
171
|
export {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
172
|
+
N as AppFooter,
|
|
173
|
+
B as AppShell,
|
|
174
|
+
C as LogConsole,
|
|
175
|
+
f as NavBar,
|
|
176
|
+
u as ThemeToggle,
|
|
177
|
+
$ as createModuleInstance,
|
|
178
|
+
x as downloadBlob,
|
|
179
|
+
A as downloadBuffer,
|
|
180
|
+
j as downloadText,
|
|
181
|
+
b as loadWasmModule
|
|
233
182
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genomicx/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shared UI components, styles, and WASM loader for GenomicX tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"dev": "vite build --watch"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"react": "^18.0.0",
|
|
27
|
-
"react-dom": "^18.0.0",
|
|
26
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
27
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
28
28
|
"react-router-dom": "^6.0.0 || ^7.0.0",
|
|
29
29
|
"react-hot-toast": "^2.0.0"
|
|
30
30
|
},
|
|
@@ -1,125 +1,400 @@
|
|
|
1
|
-
/* GenomicX
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
1
|
+
/* GenomicX component styles — no Tailwind dependency */
|
|
2
|
+
|
|
3
|
+
/* ── Base resets ─────────────────────────────── */
|
|
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
|
+
/* ── NavBar ──────────────────────────────────── */
|
|
33
|
+
.gx-nav {
|
|
34
|
+
position: sticky;
|
|
35
|
+
top: 0;
|
|
36
|
+
z-index: 40;
|
|
37
|
+
background: var(--gx-nav-bg);
|
|
38
|
+
backdrop-filter: blur(12px);
|
|
39
|
+
-webkit-backdrop-filter: blur(12px);
|
|
40
|
+
border-bottom: 1px solid var(--gx-border);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.gx-nav-inner {
|
|
44
|
+
max-width: 80rem;
|
|
45
|
+
margin: 0 auto;
|
|
46
|
+
padding: 0 1rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@media (min-width: 640px) { .gx-nav-inner { padding: 0 1.5rem; } }
|
|
50
|
+
@media (min-width: 1024px) { .gx-nav-inner { padding: 0 2rem; } }
|
|
51
|
+
|
|
52
|
+
.gx-nav-row {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
justify-content: space-between;
|
|
56
|
+
height: 60px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.gx-nav-logo {
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 0.75rem;
|
|
63
|
+
text-decoration: none;
|
|
64
|
+
opacity: 1;
|
|
65
|
+
transition: opacity var(--gx-transition);
|
|
66
|
+
}
|
|
67
|
+
.gx-nav-logo:hover { opacity: 0.85; }
|
|
68
|
+
|
|
69
|
+
.gx-nav-logo-icon { width: 28px; height: 28px; flex-shrink: 0; }
|
|
70
|
+
|
|
71
|
+
.gx-nav-logo-name {
|
|
72
|
+
font-size: 1.125rem;
|
|
73
|
+
font-weight: 700;
|
|
74
|
+
color: var(--gx-text);
|
|
75
|
+
margin: 0;
|
|
76
|
+
line-height: 1.3;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.gx-nav-logo-version {
|
|
80
|
+
font-size: 0.75rem;
|
|
81
|
+
font-weight: 400;
|
|
82
|
+
margin-left: 0.25rem;
|
|
83
|
+
color: var(--gx-text-muted);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.gx-nav-logo-sub {
|
|
87
|
+
font-size: 0.75rem;
|
|
88
|
+
color: var(--gx-text-muted);
|
|
89
|
+
margin: 0;
|
|
90
|
+
line-height: 1.2;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.gx-nav-desktop {
|
|
94
|
+
display: none;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: 1.5rem;
|
|
97
|
+
}
|
|
98
|
+
@media (min-width: 768px) { .gx-nav-desktop { display: flex; } }
|
|
99
|
+
|
|
100
|
+
.gx-nav-link {
|
|
101
|
+
font-size: 0.875rem;
|
|
102
|
+
font-weight: 500;
|
|
103
|
+
color: var(--gx-text-muted);
|
|
104
|
+
text-decoration: none;
|
|
105
|
+
transition: color var(--gx-transition);
|
|
106
|
+
display: inline-flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
gap: 0.25rem;
|
|
109
|
+
}
|
|
110
|
+
.gx-nav-link:hover { color: var(--gx-text); }
|
|
111
|
+
|
|
112
|
+
.gx-nav-link-icon { width: 14px; height: 14px; }
|
|
113
|
+
|
|
114
|
+
.gx-nav-mobile-toggle {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
gap: 0.75rem;
|
|
118
|
+
}
|
|
119
|
+
@media (min-width: 768px) { .gx-nav-mobile-toggle { display: none; } }
|
|
120
|
+
|
|
121
|
+
.gx-nav-hamburger {
|
|
122
|
+
background: none;
|
|
123
|
+
border: none;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
padding: 0.5rem;
|
|
126
|
+
border-radius: 4px;
|
|
127
|
+
color: var(--gx-text-muted);
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
}
|
|
131
|
+
.gx-nav-hamburger:hover { color: var(--gx-text); }
|
|
132
|
+
|
|
133
|
+
.gx-nav-hamburger-icon { width: 20px; height: 20px; }
|
|
134
|
+
|
|
135
|
+
.gx-nav-dropdown {
|
|
136
|
+
display: block;
|
|
137
|
+
padding: 0 1rem 1rem;
|
|
138
|
+
border-top: 1px solid var(--gx-border);
|
|
139
|
+
background: var(--gx-nav-bg);
|
|
140
|
+
}
|
|
141
|
+
@media (min-width: 768px) { .gx-nav-dropdown { display: none; } }
|
|
142
|
+
|
|
143
|
+
.gx-nav-dropdown-link {
|
|
144
|
+
display: block;
|
|
145
|
+
font-size: 0.875rem;
|
|
146
|
+
padding: 0.5rem 0;
|
|
147
|
+
color: var(--gx-text-muted);
|
|
148
|
+
text-decoration: none;
|
|
149
|
+
transition: color var(--gx-transition);
|
|
150
|
+
}
|
|
151
|
+
.gx-nav-dropdown-link:hover { color: var(--gx-text); }
|
|
152
|
+
|
|
153
|
+
/* ── Footer ──────────────────────────────────── */
|
|
154
|
+
.gx-footer {
|
|
155
|
+
margin-top: auto;
|
|
156
|
+
padding: 1.5rem 0;
|
|
157
|
+
border-top: 1px solid var(--gx-border);
|
|
158
|
+
background: var(--gx-bg-alt);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.gx-footer-inner {
|
|
162
|
+
max-width: 80rem;
|
|
163
|
+
margin: 0 auto;
|
|
164
|
+
padding: 0 1rem;
|
|
165
|
+
}
|
|
166
|
+
@media (min-width: 640px) { .gx-footer-inner { padding: 0 1.5rem; } }
|
|
167
|
+
@media (min-width: 1024px) { .gx-footer-inner { padding: 0 2rem; } }
|
|
168
|
+
|
|
169
|
+
.gx-footer-content {
|
|
170
|
+
display: flex;
|
|
171
|
+
flex-direction: column;
|
|
172
|
+
align-items: center;
|
|
173
|
+
gap: 1rem;
|
|
174
|
+
}
|
|
175
|
+
@media (min-width: 768px) {
|
|
176
|
+
.gx-footer-content { flex-direction: row; justify-content: space-between; }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.gx-footer-text { font-size: 0.875rem; color: var(--gx-text-muted); }
|
|
180
|
+
.gx-footer-text-title { font-weight: 600; color: var(--gx-text); margin: 0 0 0.25rem; }
|
|
181
|
+
.gx-footer-text-sub { margin: 0; }
|
|
182
|
+
|
|
183
|
+
.gx-footer-links {
|
|
184
|
+
display: flex;
|
|
185
|
+
gap: 1.5rem;
|
|
186
|
+
font-size: 0.875rem;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.gx-footer-link {
|
|
190
|
+
color: var(--gx-text-muted);
|
|
191
|
+
text-decoration: none;
|
|
192
|
+
background: none;
|
|
193
|
+
border: none;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
padding: 0;
|
|
196
|
+
font-size: 0.875rem;
|
|
197
|
+
transition: color var(--gx-transition);
|
|
198
|
+
}
|
|
199
|
+
.gx-footer-link:hover { color: var(--gx-accent); }
|
|
200
|
+
|
|
201
|
+
/* ── ThemeToggle ─────────────────────────────── */
|
|
202
|
+
.gx-theme-toggle {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
border-radius: 999px;
|
|
206
|
+
border: 1px solid var(--gx-border);
|
|
207
|
+
overflow: hidden;
|
|
208
|
+
font-size: 0.75rem;
|
|
209
|
+
font-weight: 500;
|
|
210
|
+
}
|
|
211
|
+
.gx-theme-toggle.disabled { opacity: 0.4; pointer-events: none; }
|
|
212
|
+
|
|
213
|
+
.gx-theme-btn {
|
|
214
|
+
display: flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
justify-content: center;
|
|
217
|
+
padding: 0.375rem 0.75rem;
|
|
218
|
+
background: none;
|
|
219
|
+
border: none;
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
transition: background var(--gx-transition), color var(--gx-transition);
|
|
222
|
+
color: var(--gx-text-muted);
|
|
223
|
+
}
|
|
224
|
+
.gx-theme-btn:hover { color: var(--gx-text); }
|
|
225
|
+
.gx-theme-btn-icon { width: 14px; height: 14px; }
|
|
226
|
+
.gx-theme-btn.active {
|
|
227
|
+
background: var(--gx-accent);
|
|
228
|
+
color: var(--gx-text-inverted);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* ── LogConsole ──────────────────────────────── */
|
|
232
|
+
.gx-console {
|
|
233
|
+
background: var(--gx-bg-alt);
|
|
234
|
+
border: 1px solid var(--gx-border);
|
|
235
|
+
border-radius: var(--gx-radius-lg);
|
|
236
|
+
padding: 1.5rem;
|
|
237
|
+
margin-top: 1.5rem;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.gx-console-progress {
|
|
241
|
+
margin-bottom: 1rem;
|
|
242
|
+
padding-bottom: 1rem;
|
|
243
|
+
border-bottom: 1px solid var(--gx-border);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.gx-console-progress-row {
|
|
247
|
+
display: flex;
|
|
248
|
+
align-items: center;
|
|
249
|
+
justify-content: space-between;
|
|
250
|
+
margin-bottom: 0.5rem;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.gx-console-progress-step { font-size: 0.875rem; font-weight: 500; color: var(--gx-text); }
|
|
254
|
+
.gx-console-progress-pct { font-size: 0.875rem; color: var(--gx-text-muted); }
|
|
255
|
+
.gx-console-progress-msg { margin-top: 0.5rem; font-size: 0.75rem; color: var(--gx-text-muted); }
|
|
256
|
+
|
|
257
|
+
.gx-console-header {
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
justify-content: space-between;
|
|
261
|
+
margin-bottom: 0.75rem;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.gx-console-title-row {
|
|
265
|
+
display: flex;
|
|
266
|
+
align-items: center;
|
|
267
|
+
gap: 0.5rem;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.gx-console-toggle {
|
|
271
|
+
background: none;
|
|
272
|
+
border: none;
|
|
273
|
+
cursor: pointer;
|
|
274
|
+
color: var(--gx-text-muted);
|
|
275
|
+
padding: 0;
|
|
276
|
+
font-size: 0.75rem;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.gx-console-title { font-weight: 600; color: var(--gx-text); margin: 0; font-size: 0.875rem; }
|
|
280
|
+
.gx-console-count { font-size: 0.75rem; color: var(--gx-text-muted); }
|
|
281
|
+
|
|
282
|
+
.gx-console-copy {
|
|
283
|
+
display: inline-flex;
|
|
284
|
+
align-items: center;
|
|
285
|
+
gap: 0.25rem;
|
|
286
|
+
font-size: 0.75rem;
|
|
287
|
+
padding: 0.25rem 0.75rem;
|
|
288
|
+
background: transparent;
|
|
289
|
+
color: var(--gx-text);
|
|
290
|
+
border: 1px solid var(--gx-border);
|
|
291
|
+
border-radius: 6px;
|
|
292
|
+
cursor: pointer;
|
|
293
|
+
transition: all var(--gx-transition);
|
|
294
|
+
font-weight: 600;
|
|
295
|
+
}
|
|
296
|
+
.gx-console-copy:hover { border-color: var(--gx-accent); color: var(--gx-accent); }
|
|
297
|
+
.gx-console-copy:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
298
|
+
.gx-console-copy-icon { width: 16px; height: 16px; }
|
|
299
|
+
|
|
300
|
+
.gx-console-body {
|
|
301
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
302
|
+
font-size: 0.75rem;
|
|
303
|
+
padding: 1rem;
|
|
304
|
+
border-radius: 6px;
|
|
305
|
+
max-height: 24rem;
|
|
306
|
+
overflow-y: auto;
|
|
307
|
+
background: var(--gx-code-bg);
|
|
308
|
+
color: var(--gx-accent);
|
|
309
|
+
border: 1px solid var(--gx-border);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.gx-console-empty { color: var(--gx-text-muted); }
|
|
313
|
+
.gx-console-line { margin-bottom: 0.25rem; white-space: pre-wrap; word-break: break-all; }
|
|
314
|
+
|
|
315
|
+
/* ── Shared utility classes ──────────────────── */
|
|
316
|
+
.card {
|
|
317
|
+
background: var(--gx-bg-alt);
|
|
318
|
+
border: 1px solid var(--gx-border);
|
|
319
|
+
border-radius: var(--gx-radius-lg);
|
|
320
|
+
padding: 1.5rem;
|
|
321
|
+
transition: border-color var(--gx-transition);
|
|
322
|
+
}
|
|
323
|
+
.card:hover { border-color: var(--gx-accent); }
|
|
324
|
+
|
|
325
|
+
.btn-primary {
|
|
326
|
+
display: inline-flex;
|
|
327
|
+
align-items: center;
|
|
328
|
+
justify-content: center;
|
|
329
|
+
gap: 0.5rem;
|
|
330
|
+
background: var(--gx-accent);
|
|
331
|
+
color: var(--gx-text-inverted);
|
|
332
|
+
font-weight: 600;
|
|
333
|
+
font-size: 0.875rem;
|
|
334
|
+
padding: 0.7rem 1.4rem;
|
|
335
|
+
border-radius: 6px;
|
|
336
|
+
border: none;
|
|
337
|
+
cursor: pointer;
|
|
338
|
+
transition: all var(--gx-transition);
|
|
339
|
+
}
|
|
340
|
+
.btn-primary:hover { background: var(--gx-accent-hover); }
|
|
341
|
+
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
342
|
+
|
|
343
|
+
.btn-secondary {
|
|
344
|
+
display: inline-flex;
|
|
345
|
+
align-items: center;
|
|
346
|
+
justify-content: center;
|
|
347
|
+
gap: 0.5rem;
|
|
348
|
+
background: transparent;
|
|
349
|
+
color: var(--gx-text);
|
|
350
|
+
font-weight: 600;
|
|
351
|
+
font-size: 0.875rem;
|
|
352
|
+
padding: 0.7rem 1.4rem;
|
|
353
|
+
border-radius: 6px;
|
|
354
|
+
border: 1px solid var(--gx-border);
|
|
355
|
+
cursor: pointer;
|
|
356
|
+
transition: all var(--gx-transition);
|
|
357
|
+
}
|
|
358
|
+
.btn-secondary:hover { border-color: var(--gx-accent); color: var(--gx-accent); }
|
|
359
|
+
|
|
360
|
+
.input-field {
|
|
361
|
+
background: var(--gx-bg);
|
|
362
|
+
border: 1px solid var(--gx-border);
|
|
363
|
+
color: var(--gx-text);
|
|
364
|
+
border-radius: 6px;
|
|
365
|
+
padding: 0.5rem 0.75rem;
|
|
366
|
+
font-size: 0.875rem;
|
|
367
|
+
transition: border-color var(--gx-transition);
|
|
368
|
+
outline: none;
|
|
369
|
+
}
|
|
370
|
+
.input-field:focus { border-color: var(--gx-accent); }
|
|
371
|
+
|
|
372
|
+
.label {
|
|
373
|
+
display: block;
|
|
374
|
+
font-size: 0.8125rem;
|
|
375
|
+
font-weight: 500;
|
|
376
|
+
color: var(--gx-text);
|
|
377
|
+
margin-bottom: 0.5rem;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.section-title {
|
|
381
|
+
font-size: 1.25rem;
|
|
382
|
+
font-weight: 700;
|
|
383
|
+
color: var(--gx-text);
|
|
384
|
+
margin-bottom: 1rem;
|
|
385
|
+
letter-spacing: -0.01em;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.progress-bg {
|
|
389
|
+
background: var(--gx-bg);
|
|
390
|
+
height: 6px;
|
|
391
|
+
border-radius: 999px;
|
|
392
|
+
overflow: hidden;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.progress-bar {
|
|
396
|
+
background: var(--gx-accent);
|
|
397
|
+
height: 6px;
|
|
398
|
+
border-radius: 999px;
|
|
399
|
+
transition: width 0.3s ease;
|
|
125
400
|
}
|