// ============================================================================= // SUMI DESIGN SYSTEM v2.0 — Source de vérité pour les tests audit // Extrait de : apps/web/src/index.css + tailwind.config.ts + composants // ============================================================================= // ============================================================================= // COULEURS // ============================================================================= export const COLORS = { dark: { bg: { void: '#0c0c0f', base: '#121215', raised: '#1a1a1f', overlay: '#222228', hover: '#2a2a31', active: '#32323a', wash: '#18181d', }, surface: { inset: '#101013', subtle: '#1e1e24', card: '#1a1a1f', elevated: '#242430', }, border: { faint: 'rgba(255,255,255, 0.06)', default: 'rgba(255,255,255, 0.10)', strong: 'rgba(255,255,255, 0.16)', focus: 'rgba(139,170,220, 0.50)', accent: 'rgba(139,170,220, 0.30)', }, text: { primary: '#f0ede8', secondary: '#a8a4a0', tertiary: '#706c68', disabled: '#4a4844', inverse: '#121215', link: '#8baade', }, accent: { DEFAULT: '#7c9dd6', hover: '#93afe0', active: '#6b8dc6', muted: 'rgba(124,157,214, 0.20)', subtle: 'rgba(124,157,214, 0.12)', emphasis: '#5a7fba', }, vermillion: { DEFAULT: '#d4634a', hover: '#de7a64', subtle: 'rgba(212,99,74, 0.12)', }, sage: { DEFAULT: '#7a9e6c', hover: '#8eb280', subtle: 'rgba(122,158,108, 0.12)', }, gold: { DEFAULT: '#c9a84c', hover: '#d6b860', subtle: 'rgba(201,168,76, 0.12)', }, live: '#e05a5a', shadows: { xs: '0 1px 2px rgba(0,0,0,0.30)', sm: '0 2px 4px rgba(0,0,0,0.25), 0 1px 2px rgba(0,0,0,0.20)', md: '0 4px 12px rgba(0,0,0,0.30), 0 2px 4px rgba(0,0,0,0.15)', lg: '0 8px 24px rgba(0,0,0,0.35), 0 4px 8px rgba(0,0,0,0.20)', xl: '0 16px 48px rgba(0,0,0,0.40), 0 8px 16px rgba(0,0,0,0.20)', '2xl': '0 24px 64px rgba(0,0,0,0.50)', glow: '0 0 0 3px rgba(124,157,214,0.25)', glowLg: '0 0 20px rgba(124,157,214,0.15)', }, glass: { bg: 'rgba(18,18,21, 0.80)', border: 'rgba(255,255,255, 0.08)', blur: '12px', }, scrollbar: { track: 'transparent', thumb: 'rgba(255,255,255, 0.10)', hover: 'rgba(255,255,255, 0.18)', }, }, light: { bg: { void: '#f0ece4', base: '#f6f3ed', raised: '#ffffff', overlay: '#ffffff', hover: '#ede9e1', active: '#e4e0d8', wash: '#f8f6f1', }, surface: { inset: '#ebe7df', subtle: '#f2eee6', card: '#ffffff', elevated: '#ffffff', }, border: { faint: 'rgba(0,0,0, 0.05)', default: 'rgba(0,0,0, 0.10)', strong: 'rgba(0,0,0, 0.18)', focus: 'rgba(80,110,170, 0.45)', accent: 'rgba(80,110,170, 0.25)', }, text: { primary: '#1a1816', secondary: '#5c5854', tertiary: '#8a8580', disabled: '#b5b0aa', inverse: '#f0ede8', link: '#4a6fa5', }, accent: { DEFAULT: '#4a6fa5', hover: '#3a5f95', active: '#5a7fb5', subtle: 'rgba(74,111,165, 0.12)', muted: 'rgba(74,111,165, 0.20)', emphasis: '#3d5f90', }, vermillion: { DEFAULT: '#b84a35', hover: '#a03e2e', subtle: 'rgba(184,74,53, 0.12)', }, sage: { DEFAULT: '#5a7e4e', hover: '#4d6e42', subtle: 'rgba(90,126,78, 0.12)', }, gold: { DEFAULT: '#9a7d2e', hover: '#8a6d20', subtle: 'rgba(154,125,46, 0.12)', }, live: '#c84040', shadows: { xs: '0 1px 2px rgba(0,0,0,0.05)', sm: '0 2px 4px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)', md: '0 4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.04)', lg: '0 8px 24px rgba(0,0,0,0.10), 0 4px 8px rgba(0,0,0,0.05)', xl: '0 16px 48px rgba(0,0,0,0.12), 0 8px 16px rgba(0,0,0,0.06)', '2xl': '0 24px 64px rgba(0,0,0,0.15)', glow: '0 0 0 3px rgba(74,111,165,0.25)', }, glass: { bg: 'rgba(255,255,255, 0.85)', border: 'rgba(0,0,0, 0.06)', }, scrollbar: { thumb: 'rgba(0,0,0, 0.12)', hover: 'rgba(0,0,0, 0.22)', }, }, contextual: { graffitiMagenta: '#c840a0', gamingGold: '#d4b040', terminalGreen: '#3eaa5e', sakura: '#e0a0b8', }, charts: { 1: '#7c9dd6', // accent 2: '#d4634a', // vermillion 3: '#7a9e6c', // sage 4: '#c9a84c', // gold 5: '#8b7ec8', // purple }, semantic: { destructiveForeground: '#ffffff', successForeground: '#ffffff', primaryForeground: '#121215', // dark theme text-inverse }, } as const; // ============================================================================= // TYPOGRAPHIE // ============================================================================= export const FONTS = { heading: { family: 'Space Grotesk', fallback: "'Space Grotesk', 'Inter', sans-serif", weights: [400, 500, 600, 700] as const, }, body: { family: 'Inter', fallback: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", weights: [300, 400, 500, 600, 700] as const, }, mono: { family: 'JetBrains Mono', fallback: "'JetBrains Mono', 'SF Mono', 'Consolas', monospace", weights: [400, 500, 600] as const, }, serif: { family: 'Noto Serif JP', fallback: "'Noto Serif JP', Georgia, serif", weights: [400, 600] as const, }, sizes: { '4xl': '2.25rem', // 36px '3xl': '1.875rem', // 30px '2xl': '1.5rem', // 24px xl: '1.25rem', // 20px lg: '1.125rem', // 18px md: '1rem', // 16px base: '0.875rem', // 14px sm: '0.8125rem', // 13px xs: '0.75rem', // 12px }, lineHeights: { none: '1', tight: '1.25', snug: '1.375', normal: '1.5', relaxed: '1.625', loose: '1.75', }, letterSpacing: { tighter: '-0.03em', tight: '-0.015em', normal: '0', wide: '0.025em', wider: '0.05em', widest: '0.1em', }, weights: { light: 300, regular: 400, medium: 500, semibold: 600, bold: 700, }, // SUMI typography presets — class → expected styles presets: { 'sumi-display': { family: 'Space Grotesk', size: '2.25rem', weight: 700, leading: 1.25, tracking: '-0.03em' }, 'sumi-h1': { family: 'Space Grotesk', size: '1.875rem', weight: 600, leading: 1.25, tracking: '-0.015em' }, 'sumi-h2': { family: 'Space Grotesk', size: '1.5rem', weight: 600, leading: 1.375, tracking: '-0.015em' }, 'sumi-h3': { family: 'Space Grotesk', size: '1.25rem', weight: 500, leading: 1.375, tracking: '0' }, 'sumi-h4': { family: 'Space Grotesk', size: '1.125rem', weight: 500, leading: 1.375, tracking: '0' }, 'sumi-body-lg': { family: 'Inter', size: '1rem', weight: 400, leading: 1.625, tracking: '0' }, 'sumi-body': { family: 'Inter', size: '0.875rem', weight: 400, leading: 1.5, tracking: '0' }, 'sumi-body-sm': { family: 'Inter', size: '0.8125rem', weight: 400, leading: 1.5, tracking: '0' }, 'sumi-caption': { family: 'Inter', size: '0.75rem', weight: 400, leading: 1.5, tracking: '0' }, 'sumi-label': { family: 'Inter', size: '0.75rem', weight: 500, leading: 1.5, tracking: '0.05em' }, 'sumi-mono': { family: 'JetBrains Mono', size: '0.8125rem', weight: 400, leading: 1.5, tracking: '0' }, }, } as const; // ============================================================================= // ESPACEMENT // ============================================================================= export const SPACING = { normal: { '0.5': '2px', '1': '4px', '1.5': '6px', '2': '8px', '2.5': '10px', '3': '12px', '4': '16px', '5': '20px', '6': '24px', '8': '32px', '10': '40px', '12': '48px', '16': '64px', '20': '80px', }, compact: { '0.5': '1.5px', '1': '3px', '1.5': '4.5px', '2': '6px', '2.5': '7.5px', '3': '9px', '4': '12px', '5': '15px', '6': '18px', '8': '24px', '10': '30px', '12': '36px', '16': '48px', '20': '60px', }, } as const; // ============================================================================= // RAYON DE BORDURE // ============================================================================= export const BORDER_RADIUS = { xs: '2px', sm: '4px', md: '6px', // --radius default lg: '12px', xl: '16px', '2xl': '20px', full: '9999px', } as const; // ============================================================================= // TRANSITIONS / MOTION // ============================================================================= export const MOTION = { duration: { instant: '75ms', fast: '150ms', normal: '200ms', slow: '300ms', slower: '500ms', }, easing: { default: 'cubic-bezier(0.25, 0.1, 0.25, 1)', out: 'cubic-bezier(0.33, 1, 0.68, 1)', in: 'cubic-bezier(0.32, 0, 0.67, 0)', inOut: 'cubic-bezier(0.65, 0, 0.35, 1)', bounce: 'cubic-bezier(0.34, 1.56, 0.64, 1)', spring: 'cubic-bezier(0.175, 0.885, 0.32, 1.1)', }, } as const; // ============================================================================= // Z-INDEX // ============================================================================= export const Z_INDEX = { base: 0, raised: 10, dropdown: 100, sticky: 200, overlay: 300, modal: 400, popover: 500, toast: 600, tooltip: 700, max: 999, // Application-specific sidebarOverlay: 90, sidebar: 95, player: 200, // var(--sumi-z-sticky) } as const; // ============================================================================= // LAYOUT // ============================================================================= export const LAYOUT = { maxWidth: { DEFAULT: '1400px', content: '1200px', narrow: '800px', prose: '65ch', layoutContent: '100rem', }, header: { height: '4rem', // 64px }, sidebar: { widthExpanded: '15rem', // 240px widthCollapsed: '5rem', // 80px offsetLeft: '1.5rem', // 24px offsetTop: '5rem', // 80px offsetBottom: '1.5rem', // 24px }, player: { height: '80px', }, main: { offsetTop: '5rem', // 80px offsetBottom: '9rem', // 144px marginLeftExpanded: '18rem', // 288px marginLeftCollapsed: '7rem', // 112px minHeight: 'calc(100vh - 4rem)', }, headerLeft: { expanded: '18rem', // 288px collapsed: '5rem', // 80px }, gaps: { DEFAULT: '1rem', // 16px sm: '0.75rem', // 12px lg: '1.5rem', // 24px }, } as const; // ============================================================================= // BREAKPOINTS // ============================================================================= export const BREAKPOINTS = { sm: 640, md: 768, lg: 1024, // Principale — sidebar responsive xl: 1280, '2xl': 1536, } as const; // ============================================================================= // VIEWPORTS DE TEST // ============================================================================= export const VIEWPORTS = { mobileSE: { width: 375, height: 667 }, mobile14: { width: 390, height: 844 }, mobilePro: { width: 430, height: 932 }, tablet: { width: 768, height: 1024 }, tabletLandscape: { width: 1024, height: 768 }, laptop: { width: 1280, height: 720 }, desktop: { width: 1440, height: 900 }, wide: { width: 1920, height: 1080 }, } as const; // ============================================================================= // ROUTES // ============================================================================= interface RouteInfo { path: string; name: string; component: string; } export const ROUTES = { /** Routes publiques — pas d'auth requise, redirige vers /dashboard si connecté */ public: [ { path: '/login', name: 'Login', component: 'LoginPage' }, { path: '/register', name: 'Register', component: 'RegisterPage' }, { path: '/forgot-password', name: 'Forgot Password', component: 'ForgotPasswordPage' }, { path: '/verify-email', name: 'Verify Email', component: 'VerifyEmailPage' }, { path: '/reset-password', name: 'Reset Password', component: 'ResetPasswordPage' }, ] satisfies RouteInfo[], /** Routes publiques standalone — pas de layout dashboard */ publicStandalone: [ { path: '/design-system', name: 'Design System', component: 'DesignSystemDemo' }, // /u/:username et /playlists/shared/:token nécessitent des params dynamiques — testés séparément ] satisfies RouteInfo[], /** Routes protégées — auth requise, DashboardLayout */ listener: [ { path: '/dashboard', name: 'Dashboard', component: 'DashboardPage' }, { path: '/feed', name: 'Feed', component: 'FeedPage' }, { path: '/discover', name: 'Discover', component: 'DiscoverPage' }, { path: '/library', name: 'Library', component: 'LibraryPage' }, { path: '/queue', name: 'Queue', component: 'QueuePage' }, { path: '/search', name: 'Search', component: 'SearchPage' }, { path: '/profile', name: 'Profile', component: 'UserProfilePage' }, { path: '/settings', name: 'Settings', component: 'SettingsPage' }, { path: '/settings/sessions', name: 'Sessions', component: 'SessionsPage' }, { path: '/notifications', name: 'Notifications', component: 'NotificationsPage' }, { path: '/playlists', name: 'Playlists', component: 'PlaylistListPage' }, { path: '/social', name: 'Social', component: 'SocialPage' }, { path: '/chat', name: 'Chat', component: 'ChatPage' }, { path: '/marketplace', name: 'Marketplace', component: 'MarketplacePage' }, { path: '/wishlist', name: 'Wishlist', component: 'WishlistPage' }, { path: '/purchases', name: 'Purchases', component: 'PurchasesPage' }, { path: '/subscription', name: 'Subscription', component: 'SubscriptionPage' }, { path: '/live', name: 'Live', component: 'LivePage' }, { path: '/cloud', name: 'Cloud', component: 'CloudPage' }, { path: '/education', name: 'Education', component: 'EducationPage' }, { path: '/support', name: 'Support', component: 'SupportPage' }, ] satisfies RouteInfo[], /** Routes créateur — auth requise, DashboardLayout */ creator: [ { path: '/analytics', name: 'Analytics', component: 'AnalyticsPage' }, { path: '/sell', name: 'Seller Dashboard', component: 'SellerDashboardPage' }, { path: '/distribution', name: 'Distribution', component: 'DistributionPage' }, { path: '/gear', name: 'Gear', component: 'GearPage' }, { path: '/live/go-live', name: 'Go Live', component: 'GoLivePage' }, { path: '/developer', name: 'Developer', component: 'DeveloperDashboardPage' }, { path: '/webhooks', name: 'Webhooks', component: 'WebhooksPage' }, ] satisfies RouteInfo[], /** Routes admin — auth requise, DashboardLayout (autorisations backend) */ admin: [ { path: '/admin', name: 'Admin Dashboard', component: 'AdminDashboardPage' }, { path: '/admin/moderation', name: 'Moderation', component: 'AdminModerationPage' }, { path: '/admin/platform', name: 'Platform', component: 'AdminPlatformPage' }, { path: '/admin/transfers', name: 'Transfers', component: 'AdminTransfersPage' }, { path: '/admin/roles', name: 'Roles', component: 'RolesPage' }, ] satisfies RouteInfo[], /** Routes d'erreur */ error: [ { path: '/404', name: 'Not Found', component: 'NotFoundPage' }, { path: '/500', name: 'Server Error', component: 'ServerErrorPage' }, ] satisfies RouteInfo[], } as const; /** Toutes les routes protégées (listener + creator + admin) */ export const ALL_PROTECTED_ROUTES = [ ...ROUTES.listener, ...ROUTES.creator, ...ROUTES.admin, ] as const; /** Toutes les routes (public + protégées + erreur) */ export const ALL_ROUTES = [ ...ROUTES.public, ...ROUTES.publicStandalone, ...ROUTES.listener, ...ROUTES.creator, ...ROUTES.admin, ...ROUTES.error, ] as const; // ============================================================================= // COMPOSANTS INTERACTIFS — sélecteurs et états attendus // ============================================================================= export const INTERACTIVE_COMPONENTS = { buttons: { default: { selector: 'button:not([disabled])', description: 'Primary CTA — bg-primary text-primary-foreground', expectedHover: { cursor: 'pointer' }, expectedFocus: { outline: '2px solid', outlineOffset: '2px' }, expectedDisabled: { opacity: '0.5', pointerEvents: 'none' }, activeScale: '0.95', }, variants: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', primary: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: 'border border-border bg-transparent hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', glass: 'bg-white/10 backdrop-blur-xl border border-white/10 text-white hover:bg-white/15', }, sizes: { default: { height: '40px', paddingX: '16px', paddingY: '8px' }, sm: { height: '36px', paddingX: '16px', borderRadius: '9999px', fontSize: '12px' }, lg: { height: '48px', paddingX: '32px', borderRadius: '9999px', fontSize: '16px' }, icon: { height: '40px', width: '40px', borderRadius: '9999px' }, }, }, inputs: { text: { selector: 'input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="number"], input[type="tel"], input[type="url"]', height: '44px', // h-11 borderRadius: '12px', // rounded-xl expectedFocus: { ring: '2px', ringColor: 'var(--ring)' }, expectedError: { borderColor: 'var(--destructive)' }, expectedDisabled: { opacity: '0.5', cursor: 'not-allowed' }, }, textarea: { selector: 'textarea', minHeight: '96px', // min-h-24 borderRadius: '8px', // rounded-lg }, checkbox: { selector: 'input[type="checkbox"]', size: '20px', // w-5 h-5 }, select: { selector: 'select, [role="listbox"], [role="combobox"]', }, }, modals: { backdrop: { selector: '[data-dialog-backdrop], .fixed.inset-0', expectedBg: 'rgba(0, 0, 0, 0.6)', expectedBlur: 'blur(4px)', }, content: { selector: '[role="dialog"]', expectedZIndex: Z_INDEX.modal, sizes: { sm: 'max-w-sm', md: 'max-w-md', lg: 'max-w-2xl', xl: 'max-w-4xl', full: 'max-w-full', }, }, }, toasts: { selector: '[data-testid="toast-alert"]', types: { success: { icon: 'CheckCircle', colorClass: 'lime' }, error: { icon: 'XCircle', colorClass: 'vermillion' }, info: { icon: 'Info', colorClass: 'cyan' }, }, animation: 'animate-slide-in-right', autoDismiss: 4000, minWidth: '288px', // min-w-72 maxWidth: '448px', // max-w-md }, sidebar: { selector: '[data-testid="app-sidebar"]', widthExpanded: '240px', widthCollapsed: '80px', zIndex: 95, overlayZIndex: 90, responsiveBreakpoint: 1024, // lg }, playerBar: { selector: '[data-testid="global-player"]', height: '80px', zIndex: 200, controls: { playPause: 'button[aria-label*="Play"], button[aria-label*="Pause"], button[aria-label*="Lire"]', previous: 'button[aria-label*="Previous"], button[aria-label*="Précédent"]', next: 'button[aria-label*="Next"], button[aria-label*="Suivant"]', shuffle: 'button[aria-label*="Shuffle"], button[aria-label*="Aléatoire"]', repeat: 'button[aria-label*="Repeat"], button[aria-label*="Répéter"]', progress: '[role="slider"][aria-label="Progression"]', volume: '[data-testid="volume-control"] [role="slider"]', }, }, cards: { track: { selector: '[role="article"]', description: 'TrackCard — used in TrackGrid on /feed, /discover', }, generic: { selector: '[class*="card"], [data-variant="card"]', defaultBorderRadius: '12px', // rounded-lg }, }, dropdowns: { container: { selector: '[role="menu"], [role="listbox"]', expectedZIndex: Z_INDEX.dropdown, borderRadius: '12px', // rounded-xl }, }, slider: { selector: '[role="slider"]', trackHeight: '4px', // h-1 trackHoverHeight: '6px', // group-hover:h-1.5 thumbSize: '20px', // h-5 w-5 thumbHiddenUntilHover: true, }, avatar: { selector: '[class*="avatar"], img[class*="rounded-full"]', sizes: { xs: '24px', // w-6 h-6 sm: '32px', // w-8 h-8 md: '40px', // w-10 h-10 lg: '48px', // w-12 h-12 xl: '64px', // w-16 h-16 '2xl': '96px', // w-24 h-24 '3xl': '128px', // w-32 h-32 }, }, badge: { selector: '[class*="badge"]', variants: ['cyan', 'magenta', 'lime', 'gold'], sizes: { sm: { paddingX: '8px', paddingY: '2px', fontSize: '12px' }, md: { paddingX: '10px', paddingY: '2px', fontSize: '12px' }, lg: { paddingX: '16px', paddingY: '4px', fontSize: '12px' }, }, }, switch: { selector: '[role="switch"]', width: '44px', // w-11 height: '24px', // h-6 thumbSize: '20px', // h-5 w-5 }, } as const; // ============================================================================= // ACCESSIBILITÉ — critères WCAG // ============================================================================= export const WCAG = { /** Ratio de contraste minimum (WCAG AA) */ contrastMinNormal: 4.5, /** Ratio de contraste minimum pour grand texte (>= 18px ou >= 14px bold) */ contrastMinLarge: 3.0, /** Taille minimale de cible tactile (WCAG 2.5.8) */ minTouchTarget: 44, /** Focus doit être visible */ focusVisible: { outlineWidth: '2px', outlineStyle: 'solid', outlineOffset: '2px', }, } as const; // ============================================================================= // SÉLECTEURS DE TEST RÉUTILISABLES // ============================================================================= export const SELECTORS = { // Layout sidebar: '[data-testid="app-sidebar"]', header: 'header, [data-testid="app-header"], [role="banner"]', playerBar: '[data-testid="global-player"]', mainContent: 'main, [role="main"]', // Auth loginForm: '[data-testid="login-form"]', registerForm: '[data-testid="register-form"]', loginSubmit: '[data-testid="login-submit"]', // Player audioElement: '[data-testid="audio-element"]', progressBar: '[role="slider"][aria-label="Progression"]', volumeSlider: '[data-testid="volume-control"] [role="slider"]', // Content trackCard: '[role="article"]', searchInput: '[data-testid="search-input"], [role="search"] input, input[type="search"], input[role="searchbox"]', // Feedback toast: '[data-testid="toast-alert"]', dialog: '[role="dialog"]', alert: '[role="alert"]', // Interactive elements (pour overlap/hover/focus tests) allInteractive: 'button, a, input, select, textarea, [role="button"], [role="link"], [role="tab"], [role="switch"], [role="slider"], [tabindex]:not([tabindex="-1"])', allButtons: 'button:visible, [role="button"]:visible', allLinks: 'a[href]:visible', allInputs: 'input:visible, textarea:visible, select:visible', } as const; // ============================================================================= // COMPTES DE TEST (seed) // ============================================================================= export const TEST_USERS = { listener: { email: 'user@veza.music', password: 'User123!', username: 'music_fan', }, creator: { email: 'artist@veza.music', password: 'Artist123!', username: 'top_artist', }, admin: { email: 'admin@veza.music', password: 'Admin123!', username: 'admin_veza', }, moderator: { email: 'mod@veza.music', password: 'Mod123!', username: 'mod_veza', }, } as const; // ============================================================================= // TIMEOUTS POUR LES TESTS // ============================================================================= export const TIMEOUTS = { navigation: 15_000, action: 5_000, animation: 1_000, networkIdle: 10_000, hoverTransition: 250, focusTransition: 150, toastDismiss: 5_000, } as const;