astrogators-shared-ui 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +137 -203
  2. package/dist/index.css +2 -0
  3. package/dist/index.js +744 -805
  4. package/dist/{components → src/components}/display/Badge.d.ts +0 -1
  5. package/dist/{components → src/components}/display/Card.d.ts +0 -1
  6. package/dist/{components → src/components}/display/Modal.d.ts +0 -1
  7. package/dist/{components → src/components}/feedback/Loader.d.ts +0 -1
  8. package/dist/{components → src/components}/forms/AllyCodeDropdown.d.ts +0 -1
  9. package/dist/{components → src/components}/forms/Button.d.ts +0 -1
  10. package/dist/{components → src/components}/forms/Input.d.ts +0 -1
  11. package/dist/{components → src/components}/forms/Select.d.ts +0 -1
  12. package/dist/{components → src/components}/layout/Container.d.ts +0 -1
  13. package/dist/{components → src/components}/layout/Footer.d.ts +0 -1
  14. package/dist/{components → src/components}/layout/TopBar.d.ts +0 -1
  15. package/dist/{contexts → src/contexts}/AuthContext.d.ts +0 -1
  16. package/dist/{index.d.ts → src/index.d.ts} +0 -1
  17. package/package.json +18 -13
  18. package/dist/style.css +0 -1
  19. /package/dist/{components → src/components}/display/index.d.ts +0 -0
  20. /package/dist/{components → src/components}/feedback/index.d.ts +0 -0
  21. /package/dist/{components → src/components}/forms/index.d.ts +0 -0
  22. /package/dist/{components → src/components}/layout/index.d.ts +0 -0
  23. /package/dist/{services → src/services}/allyCodeStorage.d.ts +0 -0
  24. /package/dist/{services → src/services}/api.d.ts +0 -0
  25. /package/dist/{services → src/services}/auth.d.ts +0 -0
  26. /package/dist/{types → src/types}/api.d.ts +0 -0
  27. /package/dist/{types → src/types}/index.d.ts +0 -0
  28. /package/dist/{types → src/types}/mod.d.ts +0 -0
  29. /package/dist/{types → src/types}/user.d.ts +0 -0
  30. /package/dist/{utils → src/utils}/formatAllyCode.d.ts +0 -0
package/README.md CHANGED
@@ -1,172 +1,114 @@
1
- # @psytor/astrogators-shared-ui
1
+ # astrogators-shared-ui
2
2
 
3
- Shared UI components and utilities for Astrogator's Table applications.
3
+ Shared React components, auth, and API client for the Astrogator's Table
4
+ frontends (`astrogators-hub`, `mod-ledger-ui`, `nightwatcher-ui`). Published
5
+ to the public npm registry as `astrogators-shared-ui` (unscoped).
6
+
7
+ > The git repo lives under `psytor/astrogators-shared-ui` on GitHub, but the
8
+ > package itself is published to **npmjs.org**, not GitHub Packages. No
9
+ > `.npmrc` scope config is needed to install.
10
+
11
+ This is a library — there is no app shell here. See `PUBLISHING.md` for the
12
+ release flow and `CLAUDE.md` for architecture notes.
4
13
 
5
14
  ## Installation
6
15
 
7
16
  ```bash
8
- npm install @psytor/astrogators-shared-ui
17
+ npm install astrogators-shared-ui
9
18
  ```
10
19
 
11
- ## Setup
12
-
13
- ### 1. Initialize API Client
20
+ Peer dependencies: `react` and `react-dom` (18 or 19).
14
21
 
15
- In your application's entry point (e.g., `main.tsx` or `App.tsx`):
16
-
17
- ```tsx
18
- import { initializeApiClient } from '@psytor/astrogators-shared-ui';
22
+ ## Setup
19
23
 
20
- initializeApiClient({
21
- baseURL: 'http://localhost:8000', // Your backend API URL
22
- onUnauthorized: () => {
23
- // Handle unauthorized (e.g., redirect to login)
24
- window.location.href = '/login';
25
- },
26
- });
27
- ```
24
+ ### 1. Wrap the app in `AuthProvider`
28
25
 
29
- ### 2. Wrap Application with AuthProvider
26
+ `AuthProvider` initializes the API client with the given `apiBaseUrl` and
27
+ manages auth, feature flags, and ally codes for the whole app. Pass the
28
+ **prefixed** backend URL — workspace backends mount their routes under
29
+ `/<service-name>` (see the workspace `CLAUDE.md`).
30
30
 
31
31
  ```tsx
32
- import { AuthProvider } from '@psytor/astrogators-shared-ui';
32
+ import { AuthProvider } from 'astrogators-shared-ui';
33
+ import 'astrogators-shared-ui/styles';
33
34
 
34
35
  function App() {
35
36
  return (
36
- <AuthProvider>
37
- {/* Your app content */}
37
+ <AuthProvider apiBaseUrl={import.meta.env.VITE_API_BASE_URL}>
38
+ {/* your app */}
38
39
  </AuthProvider>
39
40
  );
40
41
  }
41
42
  ```
42
43
 
43
- ### 3. Import Global Styles
44
-
45
- ```tsx
46
- import '@psytor/astrogators-shared-ui/styles';
47
- ```
48
-
49
- ## Components
44
+ `VITE_API_BASE_URL` looks like `http://localhost:8000/astrogators-table` in
45
+ dev.
50
46
 
51
- ### Layout Components
47
+ ### 2. (Optional) Reconfigure the API client
52
48
 
53
- #### TopBar
49
+ `AuthProvider` already calls `initializeApiClient`. Call it yourself only if
50
+ you need a custom `onUnauthorized` handler (e.g. router-driven redirects):
54
51
 
55
52
  ```tsx
56
- import { TopBar } from '@psytor/astrogators-shared-ui';
53
+ import { initializeApiClient } from 'astrogators-shared-ui';
57
54
 
58
- <TopBar
59
- logo={<div>Astrogator's Table</div>}
60
- rightContent={<button>Login</button>}
61
- />
55
+ initializeApiClient({
56
+ baseURL: import.meta.env.VITE_API_BASE_URL,
57
+ onUnauthorized: () => navigate('/login'),
58
+ });
62
59
  ```
63
60
 
64
- #### Container
65
-
66
- ```tsx
67
- import { Container } from '@psytor/astrogators-shared-ui';
61
+ ## Components
68
62
 
69
- <Container maxWidth="lg" padding>
70
- {/* Content */}
71
- </Container>
72
- ```
63
+ All components are styled via CSS Modules and the global design tokens in
64
+ `./styles`. Override the design system by redefining CSS variables on `:root`
65
+ (see "Theming").
73
66
 
74
- #### Footer
67
+ ### Layout
75
68
 
76
69
  ```tsx
77
- import { Footer } from '@psytor/astrogators-shared-ui';
70
+ import { TopBar, Container, Footer } from 'astrogators-shared-ui';
78
71
 
72
+ <TopBar logo={<Logo />} rightContent={<UserMenu />} />
73
+ <Container maxWidth="lg" padding>{children}</Container>
79
74
  <Footer />
80
75
  ```
81
76
 
82
- ### Form Components
83
-
84
- #### Button
77
+ ### Forms
85
78
 
86
79
  ```tsx
87
- import { Button } from '@psytor/astrogators-shared-ui';
80
+ import { Button, Input, Select, AllyCodeDropdown } from 'astrogators-shared-ui';
88
81
 
89
- <Button variant="primary" size="md" onClick={handleClick}>
90
- Click Me
91
- </Button>
82
+ <Button variant="primary" size="md" loading={submitting}>Save</Button>
83
+ <Input label="Email" type="email" required error={errors.email} />
84
+ <Select label="Profile" options={profiles} placeholder="Choose…" />
92
85
 
93
- <Button variant="outline" loading>
94
- Loading...
95
- </Button>
86
+ // Wired into useAuth — manages the user's ally codes (DB-backed when
87
+ // authenticated, localStorage when anonymous):
88
+ <AllyCodeDropdown />
96
89
  ```
97
90
 
98
- #### Input
91
+ `Button` variants: `primary | secondary | outline | ghost | danger`.
99
92
 
100
- ```tsx
101
- import { Input } from '@psytor/astrogators-shared-ui';
102
-
103
- <Input
104
- label="Email"
105
- type="email"
106
- placeholder="Enter your email"
107
- required
108
- error={errors.email}
109
- />
110
- ```
111
-
112
- #### Select
93
+ ### Display
113
94
 
114
95
  ```tsx
115
- import { Select } from '@psytor/astrogators-shared-ui';
116
-
117
- <Select
118
- label="Choose Profile"
119
- options={[
120
- { value: 'standard', label: 'Standard' },
121
- { value: 'speed', label: 'Speed Focus' },
122
- ]}
123
- placeholder="Select a profile"
124
- />
125
- ```
126
-
127
- ### Display Components
128
-
129
- #### Card
130
-
131
- ```tsx
132
- import { Card } from '@psytor/astrogators-shared-ui';
96
+ import { Card, Badge, Modal } from 'astrogators-shared-ui';
133
97
 
134
98
  <Card variant="elevated" chamfered chamferSize="md" padding="lg" hoverable>
135
- <h3>The Mod Ledger</h3>
136
- <p>Analyze your mods</p>
99
+
137
100
  </Card>
138
- ```
139
-
140
- #### Badge
141
-
142
- ```tsx
143
- import { Badge } from '@psytor/astrogators-shared-ui';
144
101
 
145
102
  <Badge variant="success">Active</Badge>
146
103
  <Badge variant="warning" size="sm">Beta</Badge>
147
- ```
148
104
 
149
- #### Modal
150
-
151
- ```tsx
152
- import { Modal } from '@psytor/astrogators-shared-ui';
153
-
154
- <Modal
155
- isOpen={isOpen}
156
- onClose={() => setIsOpen(false)}
157
- title="Login"
158
- size="md"
159
- >
160
- {/* Modal content */}
161
- </Modal>
105
+ <Modal isOpen={open} onClose={close} title="Login" size="md">…</Modal>
162
106
  ```
163
107
 
164
- ### Feedback Components
165
-
166
- #### Loader
108
+ ### Feedback
167
109
 
168
110
  ```tsx
169
- import { Loader } from '@psytor/astrogators-shared-ui';
111
+ import { Loader } from 'astrogators-shared-ui';
170
112
 
171
113
  <Loader size="md" variant="spinner" />
172
114
  <Loader size="lg" variant="dots" />
@@ -174,82 +116,96 @@ import { Loader } from '@psytor/astrogators-shared-ui';
174
116
 
175
117
  ## Authentication
176
118
 
177
- ### useAuth Hook
119
+ `useAuth` returns the full auth + ally-code surface. The hook must be called
120
+ inside `AuthProvider`.
178
121
 
179
122
  ```tsx
180
- import { useAuth } from '@psytor/astrogators-shared-ui';
123
+ import { useAuth } from 'astrogators-shared-ui';
181
124
 
182
- function MyComponent() {
183
- const { user, isAuthenticated, login, logout, isLoading } = useAuth();
125
+ const {
126
+ // session
127
+ user, isAuthenticated, isLoading,
128
+ login, register, logout, refreshUser,
129
+ forgotPassword, resetPassword, resendVerification,
184
130
 
185
- const handleLogin = async () => {
186
- try {
187
- await login({
188
- email: 'user@example.com',
189
- password: 'password',
190
- });
191
- } catch (error) {
192
- console.error('Login failed:', error);
193
- }
194
- };
131
+ // backend feature flags (e.g. auth_enabled)
132
+ authEnabled, isLoadingFeatures,
195
133
 
196
- if (isLoading) return <Loader />;
134
+ // ally codes DB-backed when logged in, localStorage when anonymous
135
+ allyCodes, selectedAllyCode, isLoadingAllyCodes,
136
+ fetchAllyCodes, addAllyCode, removeAllyCode,
137
+ selectAllyCode, updateAllyCodeLastUsed,
197
138
 
198
- return (
199
- <div>
200
- {isAuthenticated ? (
201
- <div>
202
- <p>Welcome, {user?.username}!</p>
203
- <button onClick={logout}>Logout</button>
204
- </div>
205
- ) : (
206
- <button onClick={handleLogin}>Login</button>
207
- )}
208
- </div>
209
- );
210
- }
139
+ // localStorage → DB migration prompt for users who sign up after
140
+ // adding ally codes anonymously
141
+ migrationPrompt, dismissMigrationPrompt, migrateLocalStorageCodes,
142
+ } = useAuth();
211
143
  ```
212
144
 
213
- ## API Client
145
+ Tokens are stored in `localStorage`. Registration does **not** auto-login —
146
+ it requires email verification.
214
147
 
215
- ### Using the API Client
148
+ ## API client
216
149
 
217
150
  ```tsx
218
- import { apiClient } from '@psytor/astrogators-shared-ui';
151
+ import { apiClient } from 'astrogators-shared-ui';
219
152
 
220
- // GET request
221
- const data = await apiClient.get('/api/v1/game-data/characters');
222
-
223
- // POST request
153
+ const characters = await apiClient.get('/api/v1/game-data/characters');
224
154
  const result = await apiClient.post('/api/v1/mod-ledger/evaluate/123456789', {
225
155
  profile_name: 'standard',
226
156
  });
227
157
  ```
228
158
 
229
- The API client automatically:
230
- - Injects JWT access token in Authorization header
231
- - Refreshes expired tokens
232
- - Retries failed requests after token refresh
233
- - Calls `onUnauthorized` callback on auth failure
159
+ The client:
160
+ - injects the access token into `Authorization`
161
+ - on `401`, transparently refreshes via `/api/v1/auth/refresh-token` and
162
+ retries the original request once
163
+ - calls `onUnauthorized` if refresh fails
164
+
165
+ Endpoints are written **without** the service prefix — the prefix lives in
166
+ the configured `baseURL`.
234
167
 
235
- ## TypeScript Types
168
+ ## Ally code utilities
236
169
 
237
- All TypeScript types are exported:
170
+ For UI that needs to format/validate the 9-digit SWGOH player IDs outside the
171
+ context of `useAuth`:
172
+
173
+ ```tsx
174
+ import {
175
+ formatAllyCode, // "123456789" → "123-456-789"
176
+ unformatAllyCode, // "123-456-789" → "123456789"
177
+ getAllyCodesFromStorage,
178
+ saveAllyCodeToStorage,
179
+ removeAllyCodeFromStorage,
180
+ getSelectedAllyCode,
181
+ setSelectedAllyCode,
182
+ clearAllyCodes,
183
+ } from 'astrogators-shared-ui';
184
+ ```
185
+
186
+ Prefer `useAuth` when you can — it keeps DB and localStorage in sync.
187
+
188
+ ## TypeScript
189
+
190
+ All public types are re-exported from the package root, including:
238
191
 
239
192
  ```tsx
240
193
  import type {
241
- User,
242
- LoginRequest,
243
- LoginResponse,
244
- ParsedMod,
245
- ModEvaluation,
246
- ApiError,
247
- } from '@psytor/astrogators-shared-ui';
194
+ User, LoginRequest, LoginResponse,
195
+ RegisterRequest, ForgotPasswordRequest, ResetPasswordRequest,
196
+ AllyCode, AllyCodeCreate, AllyCodeListResponse, StoredAllyCode,
197
+ ApiResponse, ApiError, PaginatedResponse,
198
+ ParsedMod, ModStat, ModEvaluation, EvaluationRequest, EvaluationResponse,
199
+ } from 'astrogators-shared-ui';
248
200
  ```
249
201
 
250
- ## CSS Variables
202
+ Consumers should set `"moduleResolution": "bundler"` (or `"node16"`) in
203
+ `tsconfig.json` so the bundled `.d.ts` files resolve.
251
204
 
252
- Customize the design system by overriding CSS variables:
205
+ ## Theming
206
+
207
+ Design tokens are CSS variables on `:root`. Override anything you need in
208
+ your own stylesheet, loaded after the library styles:
253
209
 
254
210
  ```css
255
211
  :root {
@@ -261,57 +217,35 @@ Customize the design system by overriding CSS variables:
261
217
  }
262
218
  ```
263
219
 
264
- ## Chamfered Boxes
220
+ ### Chamfered boxes
265
221
 
266
- Use the chamfered box effect on any element:
222
+ Sci-fi cut-corner effect, available as utility classes or via `Card`:
267
223
 
268
224
  ```tsx
269
- <div className="chamfered-box">
270
- {/* Content with cut corners */}
271
- </div>
272
-
273
- <div className="chamfered-box-lg">
274
- {/* Large chamfered corners */}
275
- </div>
225
+ <div className="chamfered-box">…</div>
226
+ <div className="chamfered-box-sm">…</div>
227
+ <div className="chamfered-box-lg">…</div>
276
228
 
277
- <div className="chamfered-box-sm">
278
- {/* Small chamfered corners */}
279
- </div>
280
- ```
281
-
282
- Or use the Card component:
283
-
284
- ```tsx
285
- <Card chamfered chamferSize="lg">
286
- {/* Content */}
287
- </Card>
229
+ <Card chamfered chamferSize="lg">…</Card>
288
230
  ```
289
231
 
290
232
  ## Development
291
233
 
292
234
  ```bash
293
- # Install dependencies
294
235
  npm install
295
-
296
- # Build library
297
- npm run build
298
-
299
- # Type check
300
- npm run type-check
236
+ npm run build # tsc && vite build → dist/
237
+ npm run type-check # tsc --noEmit
301
238
  ```
302
239
 
303
- ## Publishing
240
+ There is no `dev` server worth running (this is a library, not an app).
241
+ Iterate by rebuilding and reinstalling in a consumer.
304
242
 
305
- ```bash
306
- # Bump version
307
- npm version patch # or minor, or major
308
-
309
- # Build
310
- npm run build
311
-
312
- # Publish to GitHub Packages
313
- npm publish
314
- ```
243
+ See `PUBLISHING.md` for the release procedure. Two non-negotiable rules:
244
+ - **Always `npm run build` before `npm publish`** — `dist/` is gitignored
245
+ but is the only thing shipped, so skipping the build re-publishes stale
246
+ code.
247
+ - **`npm login` and `npm publish` must be run by the user**, not by Claude
248
+ — publishing needs interactive npm auth.
315
249
 
316
250
  ## License
317
251
 
package/dist/index.css ADDED
@@ -0,0 +1,2 @@
1
+ :root{--color-primary:#1a73e8;--color-primary-hover:#1557b0;--color-primary-active:#0d3c78;--color-secondary:#34a853;--color-secondary-hover:#2d8e47;--color-secondary-active:#1f6631;--color-background:#0a0e27;--color-background-elevated:#131729;--color-background-hover:#1a1f3a;--color-surface:#1e2139;--color-border:#2a2f4a;--color-text-primary:#fff;--color-text-secondary:#a0a5c0;--color-text-disabled:#5a5f7a;--color-success:#34a853;--color-warning:#fbbc04;--color-error:#ea4335;--color-info:#4285f4;--tier-grey:#c0eeee;--tier-green:#47c43c;--tier-blue:#1d99ff;--tier-purple:#8b5cf6;--tier-gold:#fc3;--tier-gold-glow:#fc39;--tier-purple-glow:#8b5cf680;--tier-blue-glow:#1d99ff80;--sixdot-gradient-start:#60a5fa;--sixdot-gradient-mid:#a855f7;--sixdot-gradient-end:#f59e0b;--sixdot-border-opacity:.3;--font-primary:-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;--font-heading:"Orbitron", var(--font-primary);--font-mono:"Fira Code", "Courier New", monospace;--font-size-xs:.75rem;--font-size-sm:.875rem;--font-size-base:1rem;--font-size-lg:1.125rem;--font-size-xl:1.25rem;--font-size-2xl:1.5rem;--font-size-3xl:1.875rem;--font-size-4xl:2.25rem;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--line-height-tight:1.25;--line-height-normal:1.5;--line-height-relaxed:1.75;--spacing-xs:.25rem;--spacing-sm:.5rem;--spacing-md:1rem;--spacing-lg:1.5rem;--spacing-xl:2rem;--spacing-2xl:3rem;--spacing-3xl:4rem;--radius-sm:.25rem;--radius-md:.5rem;--radius-lg:.75rem;--radius-xl:1rem;--radius-full:9999px;--shadow-sm:0 1px 2px 0 #0000004d;--shadow-md:0 4px 6px -1px #0006, 0 2px 4px -1px #0000004d;--shadow-lg:0 10px 15px -3px #00000080, 0 4px 6px -2px #0006;--shadow-xl:0 20px 25px -5px #0009, 0 10px 10px -5px #00000080;--z-base:0;--z-dropdown:100;--z-sticky:200;--z-modal:300;--z-toast:400;--z-tooltip:500;--chamfer-size:8px;--chamfer-size-lg:12px;--chamfer-size-sm:4px;--transition-fast:.15s ease-in-out;--transition-base:.2s ease-in-out;--transition-slow:.3s ease-in-out;--topbar-height:64px;--topbar-background:var(--color-background-elevated);--topbar-border:var(--color-border);--container-max-width:1280px;--container-padding:var(--spacing-md);--button-height:40px;--button-padding-x:var(--spacing-lg);--button-padding-y:var(--spacing-sm);--input-height:40px;--input-padding-x:var(--spacing-md);--input-padding-y:var(--spacing-sm);--input-border:var(--color-border);--input-background:var(--color-surface);--card-padding:var(--spacing-lg);--card-background:var(--color-surface);--card-border:var(--color-border)}*,:before,:after{box-sizing:border-box}*{margin:0;padding:0}html,body{height:100%}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{line-height:var(--line-height-normal);font-family:var(--font-primary);font-size:var(--font-size-base);color:var(--color-text-primary);background-color:var(--color-background)}img,picture,video,canvas,svg{max-width:100%;display:block}input,button,textarea,select{font:inherit;color:inherit}button{cursor:pointer;background:0 0;border:none}p,h1,h2,h3,h4,h5,h6{overflow-wrap:break-word}ul,ol{list-style:none}a{color:inherit;text-decoration:none}#root{isolation:isolate;min-height:100%}.chamfered-box{clip-path:polygon(var(--chamfer-size) 0, calc(100% - var(--chamfer-size)) 0, 100% var(--chamfer-size), 100% calc(100% - var(--chamfer-size)), calc(100% - var(--chamfer-size)) 100%, var(--chamfer-size) 100%, 0 calc(100% - var(--chamfer-size)), 0 var(--chamfer-size));position:relative}.chamfered-box-lg{clip-path:polygon(var(--chamfer-size-lg) 0, calc(100% - var(--chamfer-size-lg)) 0, 100% var(--chamfer-size-lg), 100% calc(100% - var(--chamfer-size-lg)), calc(100% - var(--chamfer-size-lg)) 100%, var(--chamfer-size-lg) 100%, 0 calc(100% - var(--chamfer-size-lg)), 0 var(--chamfer-size-lg));position:relative}.chamfered-box-sm{clip-path:polygon(var(--chamfer-size-sm) 0, calc(100% - var(--chamfer-size-sm)) 0, 100% var(--chamfer-size-sm), 100% calc(100% - var(--chamfer-size-sm)), calc(100% - var(--chamfer-size-sm)) 100%, var(--chamfer-size-sm) 100%, 0 calc(100% - var(--chamfer-size-sm)), 0 var(--chamfer-size-sm));position:relative}.chamfered-box-asymmetric{clip-path:polygon(12px 0%,calc(100% - 12px) 0%,100% 12px,100% calc(100% - 24px),calc(100% - 24px) 100%,12px 100%,0% calc(100% - 12px),0% 12px);position:relative}.chamfered-border{background:var(--color-surface);position:relative}.chamfered-border:before{content:"";background:linear-gradient(135deg, var(--color-primary), var(--color-secondary));-webkit-mask-composite:xor;clip-path:polygon(var(--chamfer-size) 0, calc(100% - var(--chamfer-size)) 0, 100% var(--chamfer-size), 100% calc(100% - var(--chamfer-size)), calc(100% - var(--chamfer-size)) 100%, var(--chamfer-size) 100%, 0 calc(100% - var(--chamfer-size)), 0 var(--chamfer-size));padding:2px;position:absolute;inset:0;-webkit-mask-image:linear-gradient(#fff 0 0),linear-gradient(#fff 0 0);-webkit-mask-position:0 0,0 0;-webkit-mask-size:auto,auto;-webkit-mask-repeat:repeat,repeat;-webkit-mask-clip:content-box,border-box;-webkit-mask-origin:content-box,border-box;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-source-type:auto,auto;mask-mode:match-source,match-source}.chamfered-diagonal-border{z-index:5;pointer-events:none;background-color:currentColor;transition:background-color .3s;position:absolute}.chamfered-diagonal-tl{transform-origin:0;width:16px;height:2px;top:-1px;left:11px;transform:rotate(135deg)}.chamfered-diagonal-tr{transform-origin:100%;width:18px;height:2px;top:10px;right:0;transform:rotate(45deg)}.chamfered-diagonal-bl{transform-origin:0;width:20px;height:2px;bottom:-2px;left:12px;transform:rotate(-135deg)}.chamfered-diagonal-br{transform-origin:100%;width:18px;height:2px;bottom:11px;right:-1px;transform:rotate(-45deg)}.chamfered-diagonal-br-large{transform-origin:100%;width:35px;height:2px;bottom:23px;right:-1px;transform:rotate(-45deg)}.gradient-primary{background:linear-gradient(135deg, var(--color-primary), var(--color-primary-active))}.gradient-secondary{background:linear-gradient(135deg, var(--color-secondary), var(--color-secondary-active))}.gradient-surface{background:linear-gradient(135deg, var(--color-surface), var(--color-background-elevated))}.glow-primary{box-shadow:0 0 20px #1a73e880}.glow-secondary{box-shadow:0 0 20px #34a85380}.glow-error{box-shadow:0 0 20px #ea433580}.glow-tier-gold{box-shadow:0 0 20px var(--tier-gold-glow)}.glow-tier-purple{box-shadow:0 0 20px var(--tier-purple-glow)}.glow-tier-blue{box-shadow:0 0 20px var(--tier-blue-glow)}.sixdot-border{position:relative}.sixdot-border:before{content:"";background:linear-gradient(45deg, var(--sixdot-gradient-start), var(--sixdot-gradient-mid), var(--sixdot-gradient-end));border-radius:inherit;opacity:var(--sixdot-border-opacity);z-index:-1;position:absolute;inset:-2px}.sixdot-glow{background:linear-gradient(135deg,#4338ca33,#7c3aed33)}.hover-scale{transition:transform var(--transition-base)}.hover-scale:hover{transform:scale(1.02)}.hover-brighten{transition:filter var(--transition-base)}.hover-brighten:hover{filter:brightness(1.1)}.hover-lift{transition:transform var(--transition-base)}.hover-lift:hover{transform:translateY(-2px)}.hover-glow{transition:box-shadow var(--transition-base)}.hover-glow:hover{box-shadow:0 0 20px #1a73e866}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.animate-fade-in{animation:fadeIn var(--transition-base)}.animate-slide-up{animation:slideUp var(--transition-base)}.animate-slide-down{animation:slideDown var(--transition-base)}.animate-spin{animation:1s linear infinite spin}.flex{display:flex}.flex-col{flex-direction:column;display:flex}.flex-center{justify-content:center;align-items:center;display:flex}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-wrap{flex-wrap:wrap}.flex-1{flex:1}.grid{display:grid}.grid-cols-2{grid-template-columns:repeat(2,1fr)}.grid-cols-3{grid-template-columns:repeat(3,1fr)}.grid-cols-4{grid-template-columns:repeat(4,1fr)}.gap-sm{gap:var(--spacing-sm)}.gap-md{gap:var(--spacing-md)}.gap-lg{gap:var(--spacing-lg)}.gap-xl{gap:var(--spacing-xl)}.m-0{margin:0}.mt-sm{margin-top:var(--spacing-sm)}.mt-md{margin-top:var(--spacing-md)}.mt-lg{margin-top:var(--spacing-lg)}.mt-xl{margin-top:var(--spacing-xl)}.mb-sm{margin-bottom:var(--spacing-sm)}.mb-md{margin-bottom:var(--spacing-md)}.mb-lg{margin-bottom:var(--spacing-lg)}.mb-xl{margin-bottom:var(--spacing-xl)}.p-sm{padding:var(--spacing-sm)}.p-md{padding:var(--spacing-md)}.p-lg{padding:var(--spacing-lg)}.p-xl{padding:var(--spacing-xl)}.text-xs{font-size:var(--font-size-xs)}.text-sm{font-size:var(--font-size-sm)}.text-base{font-size:var(--font-size-base)}.text-lg{font-size:var(--font-size-lg)}.text-xl{font-size:var(--font-size-xl)}.text-2xl{font-size:var(--font-size-2xl)}.text-3xl{font-size:var(--font-size-3xl)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-normal{font-weight:var(--font-weight-normal)}.font-medium{font-weight:var(--font-weight-medium)}.font-semibold{font-weight:var(--font-weight-semibold)}.font-bold{font-weight:var(--font-weight-bold)}.text-primary{color:var(--color-text-primary)}.text-secondary{color:var(--color-text-secondary)}.text-disabled{color:var(--color-text-disabled)}.bg-primary{background-color:var(--color-primary)}.bg-secondary{background-color:var(--color-secondary)}.bg-surface{background-color:var(--color-surface)}.bg-elevated{background-color:var(--color-background-elevated)}.hidden{display:none}.block{display:block}.inline-block{display:inline-block}.relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.w-full{width:100%}.w-auto{width:auto}.h-full{height:100%}.h-auto{height:auto}.overflow-hidden{overflow:hidden}.overflow-auto{overflow:auto}.overflow-scroll{overflow:scroll}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--color-background)}::-webkit-scrollbar-thumb{background:var(--color-border);border-radius:var(--radius-full)}::-webkit-scrollbar-thumb:hover{background:var(--color-text-disabled)}:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.bg-overlay{position:relative}.bg-overlay:before{content:"";background:var(--overlay-gradient,linear-gradient(135deg, #0f172ab3, #0f172a80));z-index:0;position:absolute;inset:0}.bg-overlay>*{z-index:1;position:relative}.backdrop-image{background-position:50%;background-repeat:no-repeat;background-size:cover;position:absolute;inset:0}._container_u3o0e_1{width:100%;margin-left:auto;margin-right:auto}._padding_u3o0e_7{padding-left:var(--container-padding);padding-right:var(--container-padding)}._maxWidth-sm_u3o0e_12{max-width:640px}._maxWidth-md_u3o0e_16{max-width:768px}._maxWidth-lg_u3o0e_20{max-width:1024px}._maxWidth-xl_u3o0e_24{max-width:var(--container-max-width)}._maxWidth-full_u3o0e_28{max-width:100%}@media (width<=768px){._padding_u3o0e_7{padding-left:var(--spacing-md);padding-right:var(--spacing-md)}}._topBar_10g3c_1{height:var(--topbar-height);background-color:var(--topbar-background);border-bottom:1px solid var(--topbar-border);z-index:var(--z-sticky);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);position:sticky;top:0;left:0;right:0}._content_10g3c_13{height:100%;max-width:var(--container-max-width);padding:0 var(--container-padding);justify-content:space-between;align-items:center;margin:0 auto;display:flex}._left_10g3c_23{align-items:center;gap:var(--spacing-xl);flex:1;display:flex}._right_10g3c_30{align-items:center;gap:var(--spacing-md);display:flex}._logo_10g3c_36{font-size:var(--font-size-xl);font-weight:var(--font-weight-bold);color:var(--color-primary);font-family:var(--font-heading);align-items:center;display:flex}@media (width<=768px){._content_10g3c_13{padding:0 var(--spacing-md)}._left_10g3c_23{gap:var(--spacing-md)}._right_10g3c_30{gap:var(--spacing-sm)}}._footer_zjuj8_1{background-color:var(--color-background-elevated);border-top:1px solid var(--color-border);margin-top:auto}._content_zjuj8_7{max-width:var(--container-max-width);padding:var(--spacing-xl) var(--container-padding);margin:0 auto}._defaultContent_zjuj8_13{align-items:center;gap:var(--spacing-sm);text-align:center;flex-direction:column;display:flex}._copyright_zjuj8_21{font-size:var(--font-size-sm);color:var(--color-text-secondary)}._disclaimer_zjuj8_26{font-size:var(--font-size-xs);color:var(--color-text-disabled)}@media (width<=768px){._content_zjuj8_7{padding:var(--spacing-lg) var(--spacing-md)}}._button_159ms_1{justify-content:center;align-items:center;gap:var(--spacing-sm);font-family:var(--font-primary);font-weight:var(--font-weight-medium);border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-base);text-align:center;-webkit-user-select:none;user-select:none;border:2px solid #0000;display:inline-flex}._button_159ms_1:disabled{opacity:.5;cursor:not-allowed}._sm_159ms_22{height:32px;padding:0 var(--spacing-md);font-size:var(--font-size-sm)}._md_159ms_28{height:var(--button-height);padding:0 var(--button-padding-x);font-size:var(--font-size-base)}._lg_159ms_34{height:48px;padding:0 var(--spacing-xl);font-size:var(--font-size-lg)}._primary_159ms_41{background-color:var(--color-primary);color:var(--color-text-primary)}._primary_159ms_41:hover:not(:disabled){background-color:var(--color-primary-hover);box-shadow:var(--shadow-md)}._primary_159ms_41:active:not(:disabled){background-color:var(--color-primary-active)}._secondary_159ms_55{background-color:var(--color-secondary);color:var(--color-text-primary)}._secondary_159ms_55:hover:not(:disabled){background-color:var(--color-secondary-hover);box-shadow:var(--shadow-md)}._secondary_159ms_55:active:not(:disabled){background-color:var(--color-secondary-active)}._outline_159ms_69{border-color:var(--color-border);color:var(--color-text-primary);background-color:#0000}._outline_159ms_69:hover:not(:disabled){background-color:var(--color-background-hover);border-color:var(--color-primary)}._ghost_159ms_80{color:var(--color-text-primary);background-color:#0000}._ghost_159ms_80:hover:not(:disabled){background-color:var(--color-background-hover)}._danger_159ms_89{background-color:var(--color-error);color:var(--color-text-primary)}._danger_159ms_89:hover:not(:disabled){box-shadow:var(--shadow-md);background-color:#c33a2e}._fullWidth_159ms_100{width:100%}._loading_159ms_105{pointer-events:none;position:relative}._loadingSpinner_159ms_110{align-items:center;gap:var(--spacing-sm);display:flex}._spinner_159ms_116{border:2px solid #0000;border-top-color:currentColor;border-radius:50%;width:16px;height:16px;animation:.6s linear infinite _spin_159ms_116}@keyframes _spin_159ms_116{to{transform:rotate(360deg)}}._loadingText_159ms_131{opacity:.7}._inputWrapper_15t4g_1{gap:var(--spacing-xs);flex-direction:column;display:flex}._fullWidth_15t4g_7{width:100%}._label_15t4g_11{font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-text-primary);align-items:center;gap:var(--spacing-xs);display:flex}._required_15t4g_20{color:var(--color-error)}._input_15t4g_1{height:var(--input-height);padding:var(--input-padding-y) var(--input-padding-x);font-size:var(--font-size-base);font-family:var(--font-primary);color:var(--color-text-primary);background-color:var(--input-background);border:1px solid var(--input-border);border-radius:var(--radius-md);transition:all var(--transition-base);outline:none}._input_15t4g_1:hover{border-color:var(--color-text-disabled)}._input_15t4g_1:focus{border-color:var(--color-primary);box-shadow:0 0 0 3px #1a73e81a}._input_15t4g_1:disabled{opacity:.5;cursor:not-allowed}._input_15t4g_1::placeholder{color:var(--color-text-disabled)}._input_15t4g_1._error_15t4g_55{border-color:var(--color-error)}._input_15t4g_1._error_15t4g_55:focus{box-shadow:0 0 0 3px #ea43351a}._errorText_15t4g_63{font-size:var(--font-size-sm);color:var(--color-error)}._helperText_15t4g_68{font-size:var(--font-size-sm);color:var(--color-text-secondary)}._selectWrapper_ir4bq_1{gap:var(--spacing-xs);flex-direction:column;display:flex}._fullWidth_ir4bq_7{width:100%}._label_ir4bq_11{font-size:var(--font-size-sm);font-weight:var(--font-weight-medium);color:var(--color-text-primary);align-items:center;gap:var(--spacing-xs);display:flex}._required_ir4bq_20{color:var(--color-error)}._selectContainer_ir4bq_24{width:100%;display:inline-block;position:relative}._select_ir4bq_1{width:100%;height:var(--input-height);padding:var(--input-padding-y) var(--input-padding-x);padding-right:calc(var(--input-padding-x) + 24px);font-size:var(--font-size-base);font-family:var(--font-primary);color:var(--color-text-primary);background-color:var(--input-background);border:1px solid var(--input-border);border-radius:var(--radius-md);transition:all var(--transition-base);cursor:pointer;appearance:none;outline:none}._select_ir4bq_1:hover{border-color:var(--color-text-disabled)}._select_ir4bq_1:focus{border-color:var(--color-primary);box-shadow:0 0 0 3px #1a73e81a}._select_ir4bq_1:disabled{opacity:.5;cursor:not-allowed}._select_ir4bq_1._error_ir4bq_63{border-color:var(--color-error)}._select_ir4bq_1._error_ir4bq_63:focus{box-shadow:0 0 0 3px #ea43351a}._arrow_ir4bq_71{right:var(--input-padding-x);pointer-events:none;font-size:var(--font-size-xs);color:var(--color-text-secondary);position:absolute;top:50%;transform:translateY(-50%)}._errorText_ir4bq_81{font-size:var(--font-size-sm);color:var(--color-error)}._helperText_ir4bq_86{font-size:var(--font-size-sm);color:var(--color-text-secondary)}._select_ir4bq_1 option{background-color:var(--color-surface);color:var(--color-text-primary)}._allyCodeDropdown_1m0tw_1{align-items:center;gap:.5rem;display:flex;position:relative}._select_1m0tw_8{min-width:200px}._manageButton_1m0tw_12{white-space:nowrap}._managePanel_1m0tw_16{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);z-index:1000;min-width:300px;margin-top:.5rem;padding:1rem;position:absolute;top:100%;right:0;box-shadow:0 4px 6px #0000001a}._managePanel_1m0tw_16 h4{color:var(--color-text-primary);margin:0 0 1rem;font-size:.875rem;font-weight:600}._addSection_1m0tw_37{border-bottom:1px solid var(--color-border);margin-bottom:1rem;padding-bottom:1rem}._addInputGroup_1m0tw_43{gap:.5rem;margin-bottom:.5rem;display:flex}._addInputGroup_1m0tw_43 input{letter-spacing:.05em;flex:1;font-family:monospace}._error_1m0tw_55{color:var(--color-error);margin-top:.25rem;font-size:.75rem}._success_1m0tw_61{color:var(--color-success);margin-top:.25rem;font-size:.75rem}._codesList_1m0tw_67{margin-bottom:1rem}._codeItem_1m0tw_71{border-bottom:1px solid var(--color-border);justify-content:space-between;align-items:center;padding:.5rem 0;display:flex}._codeItem_1m0tw_71:last-of-type{border-bottom:none}._codeItem_1m0tw_71 span{color:var(--color-text-secondary);font-size:.875rem}._doneButton_1m0tw_88{width:100%}@media (width<=768px){._select_1m0tw_8{min-width:150px}._managePanel_1m0tw_16{left:0;right:auto}}._card_b4471_1{background-color:var(--card-background);border-radius:var(--radius-lg);transition:transform var(--transition-base), box-shadow var(--transition-base)}._card_b4471_1._chamfered_b4471_8{border-radius:0}._default_b4471_13{background-color:var(--card-background)}._elevated_b4471_17{background-color:var(--card-background);box-shadow:var(--shadow-md)}._outline_b4471_22{border:1px solid var(--card-border);background-color:#0000}._padding-none_b4471_28{padding:0}._padding-sm_b4471_32{padding:var(--spacing-sm)}._padding-md_b4471_36{padding:var(--card-padding)}._padding-lg_b4471_40{padding:var(--spacing-xl)}._hoverable_b4471_45{cursor:pointer;will-change:transform;backface-visibility:hidden;transform:translateZ(0)}._hoverable_b4471_45:hover{box-shadow:var(--shadow-lg);transform:translateY(-4px)translateZ(0)}._hoverable_b4471_45:active{box-shadow:var(--shadow-md);transform:translateY(-1px)translateZ(0)}._card_b4471_1[role=button]:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}._badge_rk7t5_1{font-weight:var(--font-weight-medium);border-radius:var(--radius-full);white-space:nowrap;justify-content:center;align-items:center;display:inline-flex}._sm_rk7t5_11{font-size:var(--font-size-xs);padding:2px 8px}._md_rk7t5_16{font-size:var(--font-size-sm);padding:4px 12px}._lg_rk7t5_21{font-size:var(--font-size-base);padding:6px 16px}._default_rk7t5_27{background-color:var(--color-surface);color:var(--color-text-secondary)}._primary_rk7t5_32{background-color:var(--color-primary);color:var(--color-text-primary)}._secondary_rk7t5_37{background-color:var(--color-secondary);color:var(--color-text-primary)}._success_rk7t5_42{background-color:var(--color-success);color:var(--color-text-primary)}._warning_rk7t5_47{background-color:var(--color-warning);color:var(--color-background)}._error_rk7t5_52{background-color:var(--color-error);color:var(--color-text-primary)}._info_rk7t5_57{background-color:var(--color-info);color:var(--color-text-primary)}._overlay_1b3yt_1{z-index:var(--z-modal);padding:var(--spacing-md);animation:_fadeIn_1b3yt_1 var(--transition-base);background-color:#000000bf;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._modal_1b3yt_13{background-color:var(--color-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-xl);max-height:90vh;animation:_slideUp_1b3yt_1 var(--transition-base);flex-direction:column;display:flex;overflow:hidden}._sm_1b3yt_25{width:100%;max-width:400px}._md_1b3yt_30{width:100%;max-width:600px}._lg_1b3yt_35{width:100%;max-width:800px}._xl_1b3yt_40{width:100%;max-width:1200px}._header_1b3yt_45{padding:var(--spacing-lg);border-bottom:1px solid var(--color-border);justify-content:space-between;align-items:center;display:flex}._title_1b3yt_53{font-size:var(--font-size-xl);font-weight:var(--font-weight-semibold);color:var(--color-text-primary);margin:0}._closeButton_1b3yt_60{border-radius:var(--radius-md);width:32px;height:32px;color:var(--color-text-secondary);transition:all var(--transition-fast);font-size:var(--font-size-xl);justify-content:center;align-items:center;line-height:1;display:flex}._closeButton_1b3yt_60:hover{background-color:var(--color-background-hover);color:var(--color-text-primary)}._content_1b3yt_78{padding:var(--spacing-lg);flex:1;overflow-y:auto}@keyframes _fadeIn_1b3yt_1{0%{opacity:0}to{opacity:1}}@keyframes _slideUp_1b3yt_1{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@media (width<=768px){._modal_1b3yt_13{max-height:95vh}._header_1b3yt_45,._content_1b3yt_78{padding:var(--spacing-md)}}._spinner_1uhnh_2{border:3px solid #ffffff1a;border-top-color:var(--color-primary);border-radius:50%;animation:.8s linear infinite _spin_1uhnh_2}._spinner_1uhnh_2._sm_1uhnh_9{border-width:2px;width:16px;height:16px}._spinner_1uhnh_2._md_1uhnh_15{border-width:3px;width:32px;height:32px}._spinner_1uhnh_2._lg_1uhnh_21{border-width:4px;width:48px;height:48px}@keyframes _spin_1uhnh_2{to{transform:rotate(360deg)}}._dots_1uhnh_34{gap:8px;display:inline-flex}._dots_1uhnh_34._sm_1uhnh_9{gap:4px}._dots_1uhnh_34._lg_1uhnh_21{gap:12px}._dot_1uhnh_34{background-color:var(--color-primary);border-radius:50%;animation:1.4s ease-in-out infinite both _bounce_1uhnh_1;display:inline-block}._dots_1uhnh_34._sm_1uhnh_9 ._dot_1uhnh_34{width:6px;height:6px}._dots_1uhnh_34._md_1uhnh_15 ._dot_1uhnh_34{width:10px;height:10px}._dots_1uhnh_34._lg_1uhnh_21 ._dot_1uhnh_34{width:14px;height:14px}._dot_1uhnh_34:first-child{animation-delay:-.32s}._dot_1uhnh_34:nth-child(2){animation-delay:-.16s}@keyframes _bounce_1uhnh_1{0%,80%,to{opacity:.5;transform:scale(0)}40%{opacity:1;transform:scale(1)}}._visuallyHidden_1uhnh_91{clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}
2
+ /*$vite$:1*/