feat(story-1.2): Vue 3 SPA scaffold, base component library, User entity, SpaController
Frontend: - Vue 3 + Vite + TypeScript strict in frontend/; builds to public/build/ - Vue Router (hash-history, requiresAuth guard → /login) and Pinia - Global SCSS design tokens with 6 full themes (Warm Craft, Playful Pop, Sage & Cream, Dusty Mauve, Ocean Dusk, Honey & Slate) - Base components: BaseButton (5 variants), BaseInput (floating label, error state), BaseBottomSheet (slide-up, focus trap, tap-outside dismiss), BaseCard, BaseChip, BaseToast (2.5s auto-dismiss, aria-live polite), BottomNav (4 tabs, hides at 960px) - Type stubs for all API shapes: User, Device, Image, StickerLayer, RenderedAsset, Token Backend: - SpaController catch-all serves public/build/index.html; excludes api/setup/token/login/register - User entity (email+password+roles+theme); UserRepository with PasswordUpgrader - SecurityController with /login, /logout, /register stubs; Twig login form - security.yaml: form_login firewall, remember_me, role_hierarchy, access_control - Migration: create user table Verified: npm run build succeeds, GET / → 302 /login (unauthenticated) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
// ─── Design tokens ───────────────────────────────────────────────────────────
|
||||
|
||||
:root {
|
||||
// Typography
|
||||
--font-family: 'Nunito Variable', 'Nunito', sans-serif;
|
||||
--text-xs: 11px;
|
||||
--text-sm: 13px;
|
||||
--text-base: 15px;
|
||||
--text-md: 17px;
|
||||
--text-lg: 20px;
|
||||
--text-xl: 24px;
|
||||
--text-2xl: 28px;
|
||||
|
||||
// Spacing
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
|
||||
// Radius
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 20px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
// Motion
|
||||
--duration-fast: 150ms;
|
||||
--duration-base: 250ms;
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
|
||||
// Touch target minimum
|
||||
--touch-min: 44px;
|
||||
}
|
||||
|
||||
// ─── Themes ──────────────────────────────────────────────────────────────────
|
||||
|
||||
[data-theme="warm-craft"],
|
||||
:root {
|
||||
--color-bg: #fdf6ee;
|
||||
--color-surface: #fff9f2;
|
||||
--color-surface-2: #f5ead8;
|
||||
--color-border: #e8d9c4;
|
||||
--color-text: #3a2e22;
|
||||
--color-text-muted: #8a7060;
|
||||
--color-primary: #c97c3a;
|
||||
--color-primary-fg: #ffffff;
|
||||
--color-secondary: #e8d9c4;
|
||||
--color-secondary-fg:#3a2e22;
|
||||
--color-destructive: #c0392b;
|
||||
--color-destructive-fg: #ffffff;
|
||||
--color-focus-ring: #c97c3a;
|
||||
}
|
||||
|
||||
[data-theme="playful-pop"] {
|
||||
--color-bg: #fff0fb;
|
||||
--color-surface: #fff8fe;
|
||||
--color-surface-2: #ffe4f7;
|
||||
--color-border: #f0c8ea;
|
||||
--color-text: #2d0a28;
|
||||
--color-text-muted: #7a4272;
|
||||
--color-primary: #d63aab;
|
||||
--color-primary-fg: #ffffff;
|
||||
--color-secondary: #ffe4f7;
|
||||
--color-secondary-fg:#2d0a28;
|
||||
--color-destructive: #e03030;
|
||||
--color-destructive-fg: #ffffff;
|
||||
--color-focus-ring: #d63aab;
|
||||
}
|
||||
|
||||
[data-theme="sage-cream"] {
|
||||
--color-bg: #f6f8f3;
|
||||
--color-surface: #fafcf7;
|
||||
--color-surface-2: #e4ede0;
|
||||
--color-border: #ccd9c4;
|
||||
--color-text: #1e2b1a;
|
||||
--color-text-muted: #607050;
|
||||
--color-primary: #4e7c3a;
|
||||
--color-primary-fg: #ffffff;
|
||||
--color-secondary: #e4ede0;
|
||||
--color-secondary-fg:#1e2b1a;
|
||||
--color-destructive: #a83020;
|
||||
--color-destructive-fg: #ffffff;
|
||||
--color-focus-ring: #4e7c3a;
|
||||
}
|
||||
|
||||
[data-theme="dusty-mauve"] {
|
||||
--color-bg: #f6f0f4;
|
||||
--color-surface: #fdf8fb;
|
||||
--color-surface-2: #ead8e8;
|
||||
--color-border: #d8c4d4;
|
||||
--color-text: #2a1828;
|
||||
--color-text-muted: #7a5874;
|
||||
--color-primary: #8e4a84;
|
||||
--color-primary-fg: #ffffff;
|
||||
--color-secondary: #ead8e8;
|
||||
--color-secondary-fg:#2a1828;
|
||||
--color-destructive: #b83030;
|
||||
--color-destructive-fg: #ffffff;
|
||||
--color-focus-ring: #8e4a84;
|
||||
}
|
||||
|
||||
[data-theme="ocean-dusk"] {
|
||||
--color-bg: #eef3f8;
|
||||
--color-surface: #f4f8fc;
|
||||
--color-surface-2: #d4e4f0;
|
||||
--color-border: #b8d0e4;
|
||||
--color-text: #0e2030;
|
||||
--color-text-muted: #4a6880;
|
||||
--color-primary: #1a6ea8;
|
||||
--color-primary-fg: #ffffff;
|
||||
--color-secondary: #d4e4f0;
|
||||
--color-secondary-fg:#0e2030;
|
||||
--color-destructive: #b83020;
|
||||
--color-destructive-fg: #ffffff;
|
||||
--color-focus-ring: #1a6ea8;
|
||||
}
|
||||
|
||||
[data-theme="honey-slate"] {
|
||||
--color-bg: #f2f2ee;
|
||||
--color-surface: #f8f8f4;
|
||||
--color-surface-2: #e4e0d4;
|
||||
--color-border: #d0cc bc;
|
||||
--color-text: #1c1c18;
|
||||
--color-text-muted: #6c6858;
|
||||
--color-primary: #c49a20;
|
||||
--color-primary-fg: #1c1c18;
|
||||
--color-secondary: #e4e0d4;
|
||||
--color-secondary-fg:#1c1c18;
|
||||
--color-destructive: #b03020;
|
||||
--color-destructive-fg: #ffffff;
|
||||
--color-focus-ring: #c49a20;
|
||||
}
|
||||
|
||||
// ─── Reset & base ─────────────────────────────────────────────────────────────
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--text-base);
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100dvh;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// ─── Focus visible ────────────────────────────────────────────────────────────
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
// ─── Utility mixins ───────────────────────────────────────────────────────────
|
||||
|
||||
@mixin touch-target {
|
||||
min-height: var(--touch-min);
|
||||
min-width: var(--touch-min);
|
||||
}
|
||||
|
||||
@mixin sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
Reference in New Issue
Block a user