@svadmin/create 0.32.0 → 0.33.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.
@@ -1,41 +1,41 @@
1
1
  ---
2
2
  version: "alpha"
3
3
  name: "svadmin Admin UI"
4
- description: "A restrained, precise product interface for repeated administrative work."
4
+ description: "A restrained, precise Stripe-inspired product interface for repeated administrative work."
5
5
  colors:
6
- background: "oklch(0.985 0.001 264)"
7
- foreground: "oklch(0.205 0.012 264)"
8
- surface: "oklch(1 0 0)"
9
- surface-subtle: "oklch(0.968 0.004 264)"
10
- muted-foreground: "oklch(0.493 0.018 264)"
11
- border: "oklch(0.925 0.003 264)"
12
- primary: "oklch(0.558 0.22 278)"
13
- on-primary: "oklch(0.99 0 0)"
6
+ background: "#f6f9fc"
7
+ foreground: "#0a2540"
8
+ surface: "#ffffff"
9
+ surface-subtle: "#f1f4f8"
10
+ muted-foreground: "#425466"
11
+ border: "#e6ebf1"
12
+ primary: "#635bff"
13
+ on-primary: "#ffffff"
14
14
  success: "oklch(0.51 0.16 151)"
15
15
  warning: "oklch(0.7 0.15 75)"
16
16
  danger: "oklch(0.58 0.22 27)"
17
17
  typography:
18
18
  body:
19
- fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif"
19
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif"
20
20
  fontSize: "0.875rem"
21
21
  fontWeight: 400
22
22
  lineHeight: 1.5
23
23
  letterSpacing: "0px"
24
24
  label:
25
- fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif"
25
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif"
26
26
  fontSize: "0.8125rem"
27
27
  fontWeight: 500
28
28
  lineHeight: 1.35
29
29
  letterSpacing: "0px"
30
30
  heading:
31
- fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif"
31
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif"
32
32
  fontSize: "1.25rem"
33
33
  fontWeight: 600
34
34
  lineHeight: 1.3
35
35
  letterSpacing: "0px"
36
36
  rounded:
37
37
  sm: "4px"
38
- md: "6px"
38
+ md: "8px"
39
39
  lg: "8px"
40
40
  spacing:
41
41
  xs: "4px"
@@ -91,11 +91,13 @@ components:
91
91
 
92
92
  ## Overview
93
93
 
94
- ### Visual update: September 21, 2026
94
+ ### Visual update: September 23, 2026
95
95
 
96
- The current UI follows Metronic Tailwind demo1 for layout rhythm, light surfaces,
97
- thin borders and restrained shadows, as requested by the project owner.
98
- This supersedes the older capability-only reference limitation below.
96
+ The current UI follows a Stripe-inspired and corporate-clean visual direction:
97
+ neutral canvas, Stripe purple as the single action accent, hairline borders,
98
+ layered but restrained shadows, a subtle technical grid on the work canvas, and
99
+ short ease-out feedback for interactive controls. This supersedes the older
100
+ capability-only reference limitation below.
99
101
  Keep SVAdmin's own branding, semantic tokens, OpenUI contracts and Bits UI behavior.
100
102
  App chrome must use explicit anatomy hooks; never style every business `header`.
101
103
  Package utilities live in sublayers of `utilities`, so host-authored responsive
@@ -110,7 +112,45 @@ templates.
110
112
  Metronic is a capability reference only. Its page families, information
111
113
  architecture, and scenario coverage may expose missing components or examples,
112
114
  but its palette, decoration, card treatment, typography, and branding are not
113
- visual authority.
115
+ visual authority. Fuse, Midone and Skote are also reference-only sources for
116
+ spacing and component anatomy; their template branding and decorative treatment
117
+ must not enter the product.
118
+
119
+ ### Reference integration and precedence
120
+
121
+ The supplied references are intentionally assigned different jobs so they do
122
+ not become a mixed visual theme:
123
+
124
+ | Reference | Adopted responsibility | Explicitly excluded |
125
+ | --- | --- | --- |
126
+ | Stripe style prompt | `#635bff` action accent, technical grid, hairline borders, layered elevation, lift/depress feedback | Stripe brand assets, marketing gradients, oversized hero treatment |
127
+ | Corporate-clean prompt | neutral enterprise surfaces, readable hierarchy, generous spacing, focus-ring offset, motion capped at 200ms | blue-only palette, rigid utility-class recipes, decorative cards |
128
+ | Fuse / Midone / Skote | navigation anatomy, page-family coverage, toolbar/table/form composition, density observations | vendor palette, logos, fonts, CSS, copied markup or component chrome |
129
+ | svadmin | semantic tokens, component API, state ownership, accessibility, responsive contracts | no second visual authority |
130
+
131
+ When references conflict, svadmin semantics and accessibility win first, the
132
+ Stripe prompt controls the action accent and grid, and the corporate-clean
133
+ prompt controls restraint, spacing, and motion limits. The result is one
134
+ product language rather than a collage of template styles.
135
+
136
+ ### Runtime integration map
137
+
138
+ The same contract is used at every delivery surface:
139
+
140
+ - `packages/ui/src/app.css` owns published semantic tokens and the
141
+ `clean-flat + stripe` refinement; host applications consume this stylesheet.
142
+ - `packages/core/src/theme.svelte.ts` owns the `stripe` color preset and mode
143
+ attributes; it must stay aligned with the CSS fallback values.
144
+ - `example/src/App.svelte` is the reference application and starts with
145
+ `layoutPreset: 'clean-flat'` and `colorPreset: 'stripe'`.
146
+ - `packages/create-svadmin/template` starts new projects with the same theme
147
+ and guidance document, so generated apps do not drift from the example.
148
+ - `design/admin-ui/preview` renders the published package CSS and uses the same
149
+ Stripe preset for component and state review.
150
+
151
+ Do not add page-local palettes or shadow values when a semantic token exists.
152
+ If a new reference suggests a different treatment, record its responsibility
153
+ here before changing runtime CSS.
114
154
 
115
155
  ### Design principles
116
156
 
@@ -153,6 +193,15 @@ not a page background. Success, warning, and danger are reserved for semantic
153
193
  status and feedback. Consumer themes may replace the primary hue while keeping
154
194
  the neutral hierarchy and contrast relationships intact.
155
195
 
196
+ The reference Stripe preset uses `#635bff`, `#0a2540`, `#f6f9fc`, and
197
+ `#e6ebf1`. The grid is a low-contrast canvas texture only; buttons and data
198
+ surfaces remain solid, and gradients are not used to communicate status. The
199
+ runtime names for the shared depth and motion tokens are
200
+ `--svadmin-shadow-control`, `--svadmin-shadow-surface`,
201
+ `--svadmin-shadow-surface-hover`, `--svadmin-motion-fast`,
202
+ `--svadmin-motion-standard`, `--svadmin-motion-slow`, and
203
+ `--svadmin-focus-offset`.
204
+
156
205
  - **WCAG 2.2 AA Contrast**: Normal text maintains at least 4.5:1 against its
157
206
  canvas; large text, icons, and focus rings maintain at least 3:1. Enhanced AAA
158
207
  screens target 7:1 for normal text.
@@ -162,7 +211,7 @@ the neutral hierarchy and contrast relationships intact.
162
211
 
163
212
  ## Typography
164
213
 
165
- Use Inter or the consumer's compatible system sans. Product pages use compact
214
+ Use a compatible system sans. Product pages use compact
166
215
  headings and normal letter spacing. Uppercase table labels, negative tracking,
167
216
  and oversized dashboard numerals are not defaults.
168
217
 
@@ -197,14 +246,14 @@ may increase slightly for genuinely clickable items, without translation or
197
246
  glow. Dialogs and menus receive stronger depth because they are floating
198
247
  layers. Dark mode keeps the same hierarchy with low-chroma surfaces.
199
248
 
200
- - **Control Shadow**: `0 1px 2px rgb(15 23 42 / 0.09), 0 0 0 1px rgb(15 23 42 / 0.045)`
201
- - **Surface Shadow**: `0 1px 2px rgb(15 23 42 / 0.08), 0 10px 24px rgb(15 23 42 / 0.07)`
202
- - **Surface Hover Shadow**: `0 3px 8px rgb(15 23 42 / 0.1), 0 16px 32px rgb(15 23 42 / 0.08)`
249
+ - **Control Shadow**: `0 1px 2px rgb(15 23 42 / 0.04), 0 0 0 1px rgb(15 23 42 / 0.02)`
250
+ - **Surface Shadow**: `0 2px 4px rgb(15 23 42 / 0.04), 0 8px 16px rgb(15 23 42 / 0.08)`
251
+ - **Surface Hover Shadow**: `0 4px 8px rgb(15 23 42 / 0.06), 0 16px 32px rgb(15 23 42 / 0.1)`
203
252
  - **Overlay Shadow**: `0 20px 52px rgb(15 23 42 / 0.18), 0 6px 16px rgb(15 23 42 / 0.1)`
204
253
 
205
254
  ## Shapes
206
255
 
207
- Controls use 6px radii and bounded surfaces use 8px radii. Pills are limited to
256
+ Controls use 8px radii and bounded surfaces use 8px radii. Pills are limited to
208
257
  status badges, avatar groups, and controls whose geometry carries meaning.
209
258
 
210
259
  ## Components
@@ -219,8 +268,10 @@ focus, or elevation with hard-coded values.
219
268
 
220
269
  Transitions provide clear state confirmation without delaying user action:
221
270
 
222
- - **Timing**: Micro-transitions use 140ms–250ms with `cubic-bezier(0.16, 1, 0.3, 1)`
223
- or standard ease-out curves.
271
+ - **Timing**: Micro-transitions use 150ms–200ms with `ease-out`; never use
272
+ `ease-in-out` for routine controls.
273
+ - **Feedback**: Buttons may lift by 2px on hover and depress to `scale(0.98)`
274
+ on press. Interactive cards may lift by 4px; hover never scales a card.
224
275
  - **Focus Rings**: Operable controls declare `:focus-visible` with a 2px solid
225
276
  ring and a 2px offset.
226
277
  - **Accessibility**: All transitions and keyframe animations collapse to 0.01ms
@@ -320,6 +371,8 @@ Before accepting an AI-generated application page, verify all of the following:
320
371
  - Don't repeat a success fact across Toast, title, description, banner, badge,
321
372
  and table data.
322
373
  - Don't use persistent success banners for routine mutations.
323
- - Don't use gradients, glow, glassmorphism, negative letter spacing, or blanket
324
- `!important` overrides as the default product language.
374
+ - Don't use decorative gradients, glow, glassmorphism, negative letter spacing,
375
+ or blanket `!important` overrides as the default product language. A subtle
376
+ two-line grid is allowed on the work canvas because it is part of the current
377
+ Stripe-inspired reference direction.
325
378
  - Don't use cards inside cards or make every section a floating card.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/create",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "description": "Scaffolding tool for svadmin projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,7 +27,7 @@
27
27
  "@types/node": "^26.4.0",
28
28
  "@types/prompts": "^2.4.9",
29
29
  "typescript": "^7.0.2",
30
- "@svadmin/core": "^0.56.0"
30
+ "@svadmin/core": "^0.57.0"
31
31
  },
32
32
  "keywords": [
33
33
  "svadmin",
@@ -10,10 +10,10 @@
10
10
  "check": "svelte-check --tsconfig ./tsconfig.json"
11
11
  },
12
12
  "dependencies": {
13
- "@svadmin/core": "^0.56.0",
14
- "@svadmin/app": "^1.0.0",
15
- "@svadmin/ai-elements": "^0.10.1",
16
- "@svadmin/ui": "^0.76.0",
13
+ "@svadmin/core": "^0.57.0",
14
+ "@svadmin/app": "^0.2.0",
15
+ "@svadmin/ai-elements": "^0.10.2",
16
+ "@svadmin/ui": "^0.77.0",
17
17
  "@sinclair/typebox": "^0.34.52",
18
18
  "@tanstack/svelte-query": "^6.1.48",
19
19
  "@lucide/svelte": "^1.35.0"
@@ -31,76 +31,76 @@
31
31
  "@refinedev/core": "^5.0.12"
32
32
  },
33
33
  "simple-rest": {
34
- "@svadmin/simple-rest": "^0.12.0",
34
+ "@svadmin/simple-rest": "^0.12.1",
35
35
  "@refinedev/simple-rest": "^6.0.1"
36
36
  },
37
37
  "supabase": {
38
- "@svadmin/supabase": "^0.17.0",
38
+ "@svadmin/supabase": "^0.17.1",
39
39
  "@supabase/supabase-js": "^2.112.4",
40
40
  "@refinedev/supabase": "^6.0.2"
41
41
  },
42
42
  "graphql": {
43
- "@svadmin/graphql": "^0.12.0",
43
+ "@svadmin/graphql": "^0.12.1",
44
44
  "@refinedev/graphql": "^8.0.1",
45
45
  "graphql-request": "^7.4.0",
46
46
  "graphql": "^16.8.0"
47
47
  },
48
48
  "rest": {
49
- "@svadmin/rest": "^1.0.0",
49
+ "@svadmin/rest": "^0.2.0",
50
50
  "@refinedev/rest": "^2.1.1",
51
51
  "@nestjsx/crud-request": "^5.0.0-alpha.3"
52
52
  },
53
53
  "airtable": {
54
- "@svadmin/airtable": "^0.12.0",
54
+ "@svadmin/airtable": "^0.12.1",
55
55
  "@refinedev/airtable": "^5.0.1"
56
56
  },
57
57
  "appwrite": {
58
- "@svadmin/appwrite": "^0.11.0",
58
+ "@svadmin/appwrite": "^0.11.1",
59
59
  "appwrite": ">=26.2.0",
60
60
  "@refinedev/appwrite": "^8.0.1"
61
61
  },
62
62
  "directus": {
63
- "@svadmin/directus": "^0.11.0"
63
+ "@svadmin/directus": "^0.11.1"
64
64
  },
65
65
  "drizzle": {
66
- "@svadmin/drizzle": "^0.13.0",
66
+ "@svadmin/drizzle": "^0.13.1",
67
67
  "drizzle-orm": "^1.0.0-rc.1",
68
68
  "refine-sqlx": "^0.10.3"
69
69
  },
70
70
  "elysia": {
71
- "@svadmin/elysia": "^0.14.0",
71
+ "@svadmin/elysia": "^0.14.1",
72
72
  "elysia": ">=1.4.30",
73
73
  "@elysiajs/eden": ">=1.4.9"
74
74
  },
75
75
  "firebase": {
76
- "@svadmin/firebase": "^0.12.0"
76
+ "@svadmin/firebase": "^0.12.1"
77
77
  },
78
78
  "hasura": {
79
- "@svadmin/hasura": "^0.12.0",
79
+ "@svadmin/hasura": "^0.12.1",
80
80
  "@refinedev/hasura": "^7.0.1",
81
81
  "graphql-request": "^7.4.0"
82
82
  },
83
83
  "medusa": {
84
- "@svadmin/medusa": "^0.12.0",
84
+ "@svadmin/medusa": "^0.12.1",
85
85
  "@refinedev/medusa": "^5.0.0"
86
86
  },
87
87
  "nestjs-query": {
88
- "@svadmin/nestjs-query": "^0.12.0",
88
+ "@svadmin/nestjs-query": "^0.12.1",
89
89
  "@refinedev/nestjs-query": "^5.0.0"
90
90
  },
91
91
  "nestjsx-crud": {
92
- "@svadmin/nestjsx-crud": "^0.12.0",
92
+ "@svadmin/nestjsx-crud": "^0.12.1",
93
93
  "@refinedev/nestjsx-crud": "^6.0.1"
94
94
  },
95
95
  "pocketbase": {
96
- "@svadmin/pocketbase": "^0.12.0",
96
+ "@svadmin/pocketbase": "^0.12.1",
97
97
  "pocketbase": ">=0.28.0"
98
98
  },
99
99
  "sanity": {
100
- "@svadmin/sanity": "^0.11.0"
100
+ "@svadmin/sanity": "^0.11.1"
101
101
  },
102
102
  "strapi": {
103
- "@svadmin/strapi": "^0.12.0",
103
+ "@svadmin/strapi": "^0.12.1",
104
104
  "@refinedev/strapi-v4": "^7.0.1"
105
105
  }
106
106
  },
@@ -15,7 +15,8 @@
15
15
  resources={[...resolved.resources]}
16
16
  title="svadmin Demo"
17
17
  locale="en"
18
+ themeConfig={{ layoutPreset: 'clean-flat', colorPreset: 'stripe' }}
18
19
  >
19
20
  {#snippet dashboard()}<Dashboard />{/snippet}
20
21
  {#snippet loginPage()}<Login />{/snippet}
21
- </AdminApp>
22
+ </AdminApp>
@@ -28,58 +28,230 @@
28
28
  }
29
29
  </script>
30
30
 
31
- <div class="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
32
- <div class="w-full max-w-sm space-y-6 rounded-2xl bg-white p-8 shadow-xl shadow-blue-900/5">
31
+ <div class="scaffold-login">
32
+ <div class="scaffold-login__panel">
33
33
  <!-- Logo -->
34
- <div class="text-center">
35
- <div class="mx-auto flex h-14 w-14 items-center justify-center rounded-xl bg-primary-50">
36
- <Shield class="h-7 w-7 text-primary" />
34
+ <div class="scaffold-login__header">
35
+ <div class="scaffold-login__mark" aria-hidden="true">
36
+ <Shield size={28} strokeWidth={1.8} />
37
37
  </div>
38
- <h1 class="mt-4 text-xl font-bold text-gray-900">Admin Panel</h1>
39
- <p class="mt-1 text-sm text-gray-500">Sign in to continue</p>
38
+ <h1>Admin Panel</h1>
39
+ <p>Sign in to continue</p>
40
40
  </div>
41
41
 
42
42
  <!-- Error -->
43
43
  {#if error}
44
- <div role="alert" class="rounded-lg bg-red-50 border border-red-200 px-4 py-3 text-sm text-red-700">
44
+ <div role="alert" class="scaffold-login__error">
45
45
  {error}
46
46
  </div>
47
47
  {/if}
48
48
 
49
49
  <!-- Form -->
50
- <form onsubmit={handleLogin} class="space-y-4">
51
- <div>
52
- <label for="email" class="block text-sm font-medium text-gray-700">Email</label>
50
+ <form onsubmit={handleLogin} class="scaffold-login__form">
51
+ <label>
52
+ <span>Email</span>
53
53
  <input
54
54
  id="email"
55
55
  type="email"
56
56
  bind:value={email}
57
57
  required
58
- class="mt-1 w-full rounded-lg border border-gray-200 px-3 py-2.5 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
58
+ class="scaffold-login__input"
59
59
  placeholder="admin@example.com"
60
60
  />
61
- </div>
62
- <div>
63
- <label for="password" class="block text-sm font-medium text-gray-700">Password</label>
61
+ </label>
62
+ <label>
63
+ <span>Password</span>
64
64
  <input
65
65
  id="password"
66
66
  type="password"
67
67
  bind:value={password}
68
68
  required
69
- class="mt-1 w-full rounded-lg border border-gray-200 px-3 py-2.5 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
70
- placeholder="••••••••"
69
+ class="scaffold-login__input"
70
+ placeholder="Enter your password"
71
71
  />
72
- </div>
72
+ </label>
73
73
  <button
74
74
  type="submit"
75
75
  disabled={loading}
76
- class="flex w-full items-center justify-center gap-2 rounded-lg bg-primary py-2.5 text-sm font-medium text-white hover:bg-primary-700 disabled:opacity-50 transition"
76
+ class="scaffold-login__submit"
77
77
  >
78
78
  {#if loading}
79
- <Loader2 class="h-4 w-4 animate-spin" />
79
+ <span class="scaffold-login__spinner" aria-hidden="true"><Loader2 size={16} /></span>
80
80
  {/if}
81
81
  Sign in
82
82
  </button>
83
83
  </form>
84
84
  </div>
85
85
  </div>
86
+
87
+ <style>
88
+ .scaffold-login {
89
+ display: grid;
90
+ min-height: 100dvh;
91
+ place-items: center;
92
+ padding: 2rem 1rem;
93
+ background: var(--background);
94
+ background-image:
95
+ linear-gradient(to right, color-mix(in oklch, var(--primary) 4%, transparent) 1px, transparent 1px),
96
+ linear-gradient(to bottom, color-mix(in oklch, var(--primary) 4%, transparent) 1px, transparent 1px);
97
+ background-size: var(--svadmin-grid-size) var(--svadmin-grid-size);
98
+ color: var(--foreground);
99
+ }
100
+
101
+ .scaffold-login__panel {
102
+ width: min(100%, 28rem);
103
+ border: 1px solid var(--border);
104
+ border-radius: var(--radius-lg);
105
+ background: var(--card);
106
+ padding: 2rem;
107
+ box-shadow: var(--shadow-surface);
108
+ }
109
+
110
+ .scaffold-login__header {
111
+ display: grid;
112
+ justify-items: center;
113
+ gap: 0.5rem;
114
+ text-align: center;
115
+ }
116
+
117
+ .scaffold-login__mark {
118
+ display: grid;
119
+ width: 3.5rem;
120
+ height: 3.5rem;
121
+ place-items: center;
122
+ border: 1px solid color-mix(in oklch, var(--primary) 20%, var(--border));
123
+ border-radius: var(--radius-md);
124
+ background: color-mix(in oklch, var(--primary) 10%, var(--card));
125
+ color: var(--primary);
126
+ }
127
+
128
+ .scaffold-login__header h1 {
129
+ margin: 0.75rem 0 0;
130
+ color: var(--foreground);
131
+ font-size: 1.25rem;
132
+ font-weight: 650;
133
+ line-height: 1.3;
134
+ }
135
+
136
+ .scaffold-login__header p {
137
+ margin: 0;
138
+ color: var(--muted-foreground);
139
+ font-size: 0.875rem;
140
+ }
141
+
142
+ .scaffold-login__error {
143
+ margin-top: 1.5rem;
144
+ border: 1px solid color-mix(in oklch, var(--destructive) 28%, var(--border));
145
+ border-radius: var(--radius-md);
146
+ background: color-mix(in oklch, var(--destructive) 8%, var(--card));
147
+ color: var(--destructive);
148
+ padding: 0.75rem 1rem;
149
+ font-size: 0.875rem;
150
+ }
151
+
152
+ .scaffold-login__form {
153
+ display: grid;
154
+ gap: 1rem;
155
+ margin-top: 2rem;
156
+ }
157
+
158
+ .scaffold-login__form label {
159
+ display: grid;
160
+ gap: 0.5rem;
161
+ color: var(--foreground);
162
+ font-size: 0.875rem;
163
+ font-weight: 600;
164
+ }
165
+
166
+ .scaffold-login__input {
167
+ min-height: 2.75rem;
168
+ width: 100%;
169
+ border: 1px solid var(--border);
170
+ border-radius: var(--radius-md);
171
+ background: var(--card);
172
+ color: var(--foreground);
173
+ padding: 0.625rem 0.75rem;
174
+ font: inherit;
175
+ font-size: 0.875rem;
176
+ box-shadow: var(--shadow-control);
177
+ transition: border-color var(--svadmin-motion-fast) ease-out, box-shadow var(--svadmin-motion-fast) ease-out;
178
+ }
179
+
180
+ .scaffold-login__input::placeholder {
181
+ color: var(--muted-foreground);
182
+ opacity: 0.75;
183
+ }
184
+
185
+ .scaffold-login__input:focus-visible {
186
+ border-color: var(--ring);
187
+ outline: 2px solid color-mix(in oklch, var(--ring) 42%, transparent);
188
+ outline-offset: var(--svadmin-focus-offset);
189
+ }
190
+
191
+ .scaffold-login__submit {
192
+ display: inline-flex;
193
+ min-height: 2.75rem;
194
+ align-items: center;
195
+ justify-content: center;
196
+ gap: 0.5rem;
197
+ width: 100%;
198
+ border: 1px solid color-mix(in oklch, var(--primary) 70%, var(--border));
199
+ border-radius: var(--radius-md);
200
+ background: var(--primary);
201
+ color: var(--primary-foreground);
202
+ padding: 0.625rem 1rem;
203
+ font: inherit;
204
+ font-size: 0.875rem;
205
+ font-weight: 650;
206
+ box-shadow: var(--shadow-control);
207
+ transition: transform var(--svadmin-motion-standard) ease-out, box-shadow var(--svadmin-motion-standard) ease-out, background-color var(--svadmin-motion-standard) ease-out;
208
+ }
209
+
210
+ .scaffold-login__submit:hover:not(:disabled) {
211
+ transform: translateY(-0.125rem);
212
+ }
213
+
214
+ .scaffold-login__submit:active:not(:disabled) {
215
+ transform: translateY(0) scale(0.98);
216
+ box-shadow: var(--shadow-control);
217
+ }
218
+
219
+ .scaffold-login__submit:focus-visible {
220
+ outline: 2px solid color-mix(in oklch, var(--ring) 78%, transparent);
221
+ outline-offset: var(--svadmin-focus-offset);
222
+ }
223
+
224
+ .scaffold-login__submit:disabled {
225
+ cursor: not-allowed;
226
+ opacity: 0.55;
227
+ }
228
+
229
+ .scaffold-login__spinner {
230
+ animation: scaffold-login-spin var(--svadmin-motion-slow) linear infinite;
231
+ }
232
+
233
+ @keyframes scaffold-login-spin {
234
+ to { transform: rotate(360deg); }
235
+ }
236
+
237
+ @media (prefers-reduced-motion: reduce) {
238
+ .scaffold-login__input,
239
+ .scaffold-login__submit {
240
+ transition-duration: 0.01ms;
241
+ }
242
+
243
+ .scaffold-login__spinner {
244
+ animation-duration: 0.01ms;
245
+ }
246
+ }
247
+
248
+ @media (max-width: 32rem) {
249
+ .scaffold-login {
250
+ padding: 1rem;
251
+ }
252
+
253
+ .scaffold-login__panel {
254
+ padding: 1.5rem;
255
+ }
256
+ }
257
+ </style>