prod: UI Optimisée mise en production
- Documentation archivée et réorganisée - Backend: Ajout tests, migrations, library service, rate limiting - Frontend: Suppression Flutter, focus sur interface web HTML/JS - Tailwind CSS ajouté pour le style - Améliorations UX et corrections bugs Generated with [Claude Code](https://claude.com/claude-code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -0,0 +1,884 @@
|
||||
# AudiOhm - Guide de Style Complet
|
||||
|
||||
**Version:** 1.0
|
||||
**Date:** 2026-01-18
|
||||
**Thème:** Cyberpunk Néon Moderne
|
||||
|
||||
---
|
||||
|
||||
## 📋 Table des Matières
|
||||
|
||||
1. [Vue d'ensemble](#vue-densemble)
|
||||
2. [Système de Design](#système-de-design)
|
||||
3. [Typography](#typography)
|
||||
4. [Couleurs](#couleurs)
|
||||
5. [Espacing](#espacement)
|
||||
6. [Composants](#composants)
|
||||
7. [Animations](#animations)
|
||||
8. [Patterns](#patterns)
|
||||
9. [Best Practices](#best-practices)
|
||||
10. [Anti-Patterns](#anti-patterns)
|
||||
|
||||
---
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
### Identité de Marque
|
||||
|
||||
**AudiOhm** est une plateforme de streaming musicale alternative avec une esthétique **cyberpunk moderne**.
|
||||
|
||||
**Mots-clés:**
|
||||
- Néon
|
||||
- Futuriste
|
||||
- Énergique
|
||||
- Immersif
|
||||
- Premium
|
||||
|
||||
**Principes de Design:**
|
||||
- **Contraste élevé** - Lisibilité maximale
|
||||
- **Feedback immédiat** - Réponse à chaque interaction
|
||||
- **Transitions fluides** - Animations 200ms
|
||||
- **Glow néon** - Effets lumineux subtils
|
||||
- **Accessibilité** - WCAG AA compliant
|
||||
|
||||
---
|
||||
|
||||
## Système de Design
|
||||
|
||||
### Palette de Couleurs
|
||||
|
||||
#### Backgrounds
|
||||
|
||||
```dart
|
||||
// Couleurs de fond
|
||||
background #0A0E27 // Fond principal (bleu nuit très foncé)
|
||||
surface #151932 // Surfaces (cards, panels)
|
||||
surfaceElevated #1F2342 // Surfaces élevées (hover)
|
||||
border #2A2F4A // Bordures, diviseurs
|
||||
```
|
||||
|
||||
#### Néon Accents
|
||||
|
||||
```dart
|
||||
// Couleurs néon pour accents
|
||||
primary #00F0FF // Cyan électrique (CTA principal)
|
||||
secondary #BF00FF // Violet/magenta (actions secondaires)
|
||||
accent #FF006E // Rose néon (highlights, likes)
|
||||
success #00FF94 // Vert néon matrix (success states)
|
||||
warning #FFB800 // Jaune néon (warnings)
|
||||
error #FF3B3B // Rouge néon (errors)
|
||||
```
|
||||
|
||||
#### Text
|
||||
|
||||
```dart
|
||||
// Couleurs de texte
|
||||
textPrimary #F0F4F8 // Titres, texte principal (14:1 contrast)
|
||||
textSecondary #9BA3B8 // Sous-titres, descriptions (4.8:1 contrast)
|
||||
textTertiary #6B7280 // Tertiaire, disabled (4.5:1 contrast)
|
||||
textInverted #0A0E27 // Texte sur fond néon
|
||||
```
|
||||
|
||||
#### Gradients
|
||||
|
||||
```dart
|
||||
// Dégradés prédéfinis
|
||||
gradientPrimary: LinearGradient(135deg, #00F0FF, #BF00FF)
|
||||
gradientAccent: LinearGradient(135deg, #BF00FF, #FF006E)
|
||||
gradientFull: LinearGradient(-1,-1 → 1,1, #00F0FF, #BF00FF, #FF006E)
|
||||
gradientSurface: LinearGradient(180deg, rgba(21,25,50,0.9), rgba(10,14,39,0.95))
|
||||
```
|
||||
|
||||
#### Effets de Glow
|
||||
|
||||
```dart
|
||||
// Ombres néon
|
||||
glowPrimary(color: AppColors.primary) // BoxShadow avec cyan
|
||||
glowSecondary(color: AppColors.secondary) // BoxShadow avec violet
|
||||
glowAccent(color: AppColors.accent) // BoxShadow avec rose
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
### Font Families
|
||||
|
||||
```dart
|
||||
// Fonts importés de Google Fonts
|
||||
fontHeading: 'Space Grotesk' // Titres, headings
|
||||
fontBody: 'Outfit' // Corps de texte
|
||||
fontMono: 'JetBrains Mono' // Code, détails techniques
|
||||
|
||||
// Imports
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
GoogleFonts.spaceGrotesk(fontWeight: FontWeight.w700)
|
||||
GoogleFonts.outfit(fontWeight: FontWeight.w400)
|
||||
```
|
||||
|
||||
### Type Scale
|
||||
|
||||
| Role | Size | Weight | Line-Height | Usage |
|
||||
|------|------|--------|-------------|-------|
|
||||
| **Display** | 48px | 700 | 1.1 | Hero section, grands titres |
|
||||
| **H1** | 36px | 700 | 1.2 | Titres de pages |
|
||||
| **H2** | 28px | 600 | 1.3 | Sections |
|
||||
| **H3** | 22px | 600 | 1.4 | Sous-sections, card titles |
|
||||
| **Body Large** | 18px | 400 | 1.5 | Texte important |
|
||||
| **Body** | 16px | 400 | 1.6 | Texte standard |
|
||||
| **Body Small** | 14px | 400 | 1.6 | Texte secondaire |
|
||||
| **Caption** | 12px | 500 | 1.5 | Métadonnées |
|
||||
| **Overline** | 11px | 600 | 1.4 | Labels, tags, UPPERCASE |
|
||||
|
||||
### Règles Typography
|
||||
|
||||
1. **Contraste minimum** - 4.5:1 pour body text, 3:1 pour large text
|
||||
2. **Line-height** - 1.5-1.75 pour body text
|
||||
3. **Max line length** - 65-75 caractères pour lisibilité optimale
|
||||
4. **Font pairings** - Utiliser Space Grotesk pour headings, Outfit pour body
|
||||
5. **No font mix** - Ne pas mélanger plus de 2 fonts par page
|
||||
|
||||
---
|
||||
|
||||
## Espacement
|
||||
|
||||
### Système de Spacing
|
||||
|
||||
Base: **4px** - Tous les espacements sont des multiples de 4
|
||||
|
||||
```dart
|
||||
spacing1 4px // Gaps serrés, icon padding
|
||||
spacing2 8px // Small gaps, button padding compact
|
||||
spacing3 12px // Card padding compact, gaps
|
||||
spacing4 16px // Standard spacing, card padding
|
||||
spacing5 20px // Medium gaps
|
||||
spacing6 24px // Section padding, form fields
|
||||
spacing8 32px // Large gaps, content sections
|
||||
spacing10 40px // XL gaps, page padding
|
||||
spacing12 48px // XXL gaps, major sections
|
||||
spacing16 64px // Hero sections, page margins
|
||||
```
|
||||
|
||||
### Padding Standards
|
||||
|
||||
```dart
|
||||
// Cards
|
||||
paddingSmall: 12px // Compact cards
|
||||
paddingMedium: 16px // Standard cards
|
||||
paddingLarge: 20px // Large cards, featured cards
|
||||
|
||||
// Pages
|
||||
paddingPage: 24px // Pages standard
|
||||
paddingPageLg: 32px // Pages avec plus d'espace
|
||||
|
||||
// Sections
|
||||
gapSection: 48px // Espace entre sections majeures
|
||||
gapSubsection: 24px // Espace entre sous-sections
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Composants
|
||||
|
||||
### Buttons
|
||||
|
||||
#### Primary Button (Cyan Néon)
|
||||
|
||||
```dart
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppColors.gradientPrimary,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: AppColors.glowPrimary,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
child: Text(
|
||||
'Button Text',
|
||||
style: TextStyle(
|
||||
color: AppColors.textInverted,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
**Règles:**
|
||||
- Background: Gradient cyan → violet
|
||||
- Text: Blanc sur fond néon
|
||||
- Glow: BoxShadow avec cyan
|
||||
- Hover: Glow intensifié
|
||||
- Padding: 24px horizontal, 12px vertical
|
||||
- Border radius: 8px
|
||||
|
||||
#### Secondary Button (Ghost)
|
||||
|
||||
```dart
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.primary, width: 1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
child: Text(
|
||||
'Button Text',
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
**Règles:**
|
||||
- Background: Transparent
|
||||
- Border: 1px cyan
|
||||
- Text: Cyan
|
||||
- Hover: Background cyan avec 10% opacity
|
||||
- Glow: Optional sur hover
|
||||
|
||||
#### Icon Buttons
|
||||
|
||||
```dart
|
||||
// Size standards
|
||||
iconSizeSmall: 16px
|
||||
iconSizeMedium: 20px
|
||||
iconSizeLarge: 24px
|
||||
iconSizeXL: 32px
|
||||
|
||||
// Touch targets
|
||||
minTouchTarget: 44x44px // Mobile
|
||||
minClickTarget: 40x40px // Desktop
|
||||
```
|
||||
|
||||
### Cards
|
||||
|
||||
#### Base Card
|
||||
|
||||
```dart
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppColors.border,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: /* Card content */,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
#### Interactive Card (avec Hover)
|
||||
|
||||
```dart
|
||||
class InteractiveCard extends StatefulWidget {
|
||||
// ...
|
||||
}
|
||||
|
||||
class _InteractiveCardState extends State<InteractiveCard> {
|
||||
bool _isHovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _isHovered = true),
|
||||
onExit: (_) => setState(() => _isHovered = false),
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: _isHovered
|
||||
? AppColors.primary
|
||||
: AppColors.border,
|
||||
width: _isHovered ? 2 : 1,
|
||||
),
|
||||
boxShadow: _isHovered
|
||||
? [
|
||||
BoxShadow(
|
||||
color: AppColors.primary.withOpacity(0.15),
|
||||
blurRadius: 20,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: /* Card content */,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Règles:**
|
||||
- Background: `surface`
|
||||
- Border: `border` (1px)
|
||||
- Border radius: 16px
|
||||
- Padding: 20px
|
||||
- Hover: Border accent + glow
|
||||
|
||||
#### Album Card
|
||||
|
||||
```dart
|
||||
// Dimensions
|
||||
albumCardSmall: 120x120px // Mobile, compact grids
|
||||
albumCardMedium: 160x160px // Tablet, desktop
|
||||
albumCardLarge: 200x200px // Featured, hero
|
||||
|
||||
// Aspect ratios
|
||||
albumArtSquare: 1:1
|
||||
playlistCover: 1:1
|
||||
artistThumbnail: 1:1
|
||||
```
|
||||
|
||||
### Inputs
|
||||
|
||||
#### Text Field
|
||||
|
||||
```dart
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: AppColors.background,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: AppColors.border, width: 2),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: AppColors.border, width: 2),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: AppColors.primary, width: 2),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
hintStyle: TextStyle(
|
||||
color: AppColors.textTertiary,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
**Règles:**
|
||||
- Background: `background` (plus foncé que surface)
|
||||
- Border: `border` (2px)
|
||||
- Border radius: 8px
|
||||
- Focus: Border devient `primary` avec glow
|
||||
- Padding: 16px horizontal, 12px vertical
|
||||
- Hint: `textTertiary`
|
||||
|
||||
---
|
||||
|
||||
## Animations
|
||||
|
||||
### Durées Standard
|
||||
|
||||
```dart
|
||||
// Timing
|
||||
fast: Duration(milliseconds: 150) // Micro-interactions
|
||||
base: Duration(milliseconds: 200) // Hover, color changes
|
||||
slow: Duration(milliseconds: 300) // Layout changes, modals
|
||||
slower: Duration(milliseconds: 500) // Page transitions
|
||||
```
|
||||
|
||||
### Curves
|
||||
|
||||
```dart
|
||||
// Easing curves
|
||||
curveEaseOut: Curves.easeOut // Sortie fluide
|
||||
curveEaseInOut: Curves.easeInOut // Entrée-sortie fluide
|
||||
curveBounce: Curves.elasticOut // Effet rebond (play button)
|
||||
```
|
||||
|
||||
### Transitions
|
||||
|
||||
#### Hover States
|
||||
|
||||
```dart
|
||||
AnimatedContainer(
|
||||
duration: Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
decoration: BoxDecoration(
|
||||
// Changes to animate
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
#### Page Transitions
|
||||
|
||||
```dart
|
||||
// Slide up (modals, sheets)
|
||||
PageRouteBuilder(
|
||||
pageBuilder: (context, animation, _) => child,
|
||||
transitionDuration: Duration(milliseconds: 300),
|
||||
transitionsBuilder: (context, animation, _, child) {
|
||||
const begin = Offset(0.0, 1.0);
|
||||
const end = Offset.zero;
|
||||
final tween = Tween(begin: begin, end: end);
|
||||
return SlideTransition(
|
||||
position: animation.drive(tween),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
)
|
||||
|
||||
// Fade (page changes)
|
||||
PageRouteBuilder(
|
||||
pageBuilder: (context, animation, _) => child,
|
||||
transitionDuration: Duration(milliseconds: 200),
|
||||
transitionsBuilder: (context, animation, _, child) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Loading States
|
||||
|
||||
#### Skeleton Loading
|
||||
|
||||
```dart
|
||||
Shimmer.fromColors(
|
||||
baseColor: AppColors.surfaceVariant,
|
||||
highlightColor: AppColors.surfaceElevated,
|
||||
child: /* Skeleton content */,
|
||||
)
|
||||
```
|
||||
|
||||
#### Progress Indicator
|
||||
|
||||
```dart
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primary),
|
||||
backgroundColor: AppColors.surfaceVariant,
|
||||
strokeWidth: 3,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Patterns
|
||||
|
||||
### Hover State Pattern
|
||||
|
||||
```dart
|
||||
// Template pour widgets avec hover
|
||||
class HoverableWidget extends StatefulWidget {
|
||||
final Widget child;
|
||||
final VoidCallback? onTap;
|
||||
final Color hoverColor;
|
||||
|
||||
@override
|
||||
State<HoverableWidget> createState() => _HoverableWidgetState();
|
||||
}
|
||||
|
||||
class _HoverableWidgetState extends State<HoverableWidget> {
|
||||
bool _isHovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _isHovered = true),
|
||||
onExit: (_) => setState(() => _isHovered = false),
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _isHovered
|
||||
? widget.hoverColor
|
||||
: AppColors.border,
|
||||
width: _isHovered ? 2 : 1,
|
||||
),
|
||||
boxShadow: _isHovered
|
||||
? [
|
||||
BoxShadow(
|
||||
color: widget.hoverColor.withOpacity(0.3),
|
||||
blurRadius: 20,
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Clickable Wrapper Pattern
|
||||
|
||||
```dart
|
||||
// Wrapper pour ajouter cursor pointer
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => /* action */,
|
||||
child: /* widget */,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### Error Display Pattern
|
||||
|
||||
```dart
|
||||
// Affichage user-friendly des erreurs
|
||||
if (errorMessage != null) {
|
||||
Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.error.withOpacity(0.1),
|
||||
border: Border.all(
|
||||
color: AppColors.error.withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.error_outline, color: AppColors.error, size: 20),
|
||||
SizedBox(width: 8),
|
||||
Expanded(child: Text(errorMessage)),
|
||||
if (onRetry != null)
|
||||
TextButton(
|
||||
onPressed: onRetry,
|
||||
child: Text('Retry'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Accessibilité
|
||||
|
||||
#### Contrast
|
||||
|
||||
```dart
|
||||
// TOUJOURS vérifier le contraste
|
||||
✅ Bon: textPrimary (#F0F4F8) sur background (#0A0E27) = 14:1
|
||||
✅ Bon: textSecondary (#9BA3B8) sur background (#0A0E27) = 4.8:1
|
||||
❌ Mauvais: muted (#6A7294) sur background (#0A0E27) = 2.1:1
|
||||
```
|
||||
|
||||
#### Touch Targets
|
||||
|
||||
```dart
|
||||
// Minimum sizes
|
||||
mobileMinTapTarget: 44x44px
|
||||
desktopMinClickTarget: 40x40px
|
||||
```
|
||||
|
||||
#### Focus States
|
||||
|
||||
```dart
|
||||
// Toujours montrer le focus
|
||||
InputDecoration(
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: AppColors.primary, width: 2),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Performance
|
||||
|
||||
#### Images
|
||||
|
||||
```dart
|
||||
// Utiliser cached_network_image
|
||||
CachedNetworkImage(
|
||||
imageUrl: url,
|
||||
placeholder: (context, url) => CircularProgressIndicator(),
|
||||
errorWidget: (context, url, error) => Icon(Icons.error),
|
||||
)
|
||||
|
||||
// OU utiliser le widget personnalisé
|
||||
AlbumArtImage(imageUrl: url)
|
||||
```
|
||||
|
||||
#### Animations
|
||||
|
||||
```dart
|
||||
// Utiliser AnimatedContainer au lieu de AnimationController
|
||||
AnimatedContainer(
|
||||
duration: Duration(milliseconds: 200),
|
||||
// Props to animate
|
||||
)
|
||||
|
||||
// Pour reduced-motion
|
||||
if (MediaQuery.of(context).disableAnimations) {
|
||||
// Skip animations
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Responsive
|
||||
|
||||
```dart
|
||||
// Breakpoints
|
||||
mobile: < 768px
|
||||
tablet: 768px - 1024px
|
||||
desktop: >= 1024px
|
||||
|
||||
// Exemple
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (constraints.maxWidth >= 1024) {
|
||||
return DesktopLayout();
|
||||
} else if (constraints.maxWidth >= 768) {
|
||||
return TabletLayout();
|
||||
} else {
|
||||
return MobileLayout();
|
||||
}
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
### ❌ À ÉVITER
|
||||
|
||||
#### 1. Emojis comme Icônes
|
||||
|
||||
```dart
|
||||
❌ Mauvais
|
||||
Icon(Icons.emoji_events) // N'utilisez pas d'emojis
|
||||
Text('🎵 Music')
|
||||
|
||||
✅ Bon
|
||||
Icon(Icons.music_note)
|
||||
Icon(Icons.audiotrack)
|
||||
```
|
||||
|
||||
#### 2. Text Contrast Faible
|
||||
|
||||
```dart
|
||||
❌ Mauvais
|
||||
TextStyle(
|
||||
color: Color(0xFF6A7294), // Trop sombre pour dark mode
|
||||
)
|
||||
|
||||
✅ Bon
|
||||
TextStyle(
|
||||
color: AppColors.textSecondary, #9BA3B8 - Contrast 4.8:1
|
||||
)
|
||||
```
|
||||
|
||||
#### 3. Transitions Instantanées
|
||||
|
||||
```dart
|
||||
❌ Mauvais
|
||||
duration: Duration(milliseconds: 0)
|
||||
duration: Duration(milliseconds: 100) // Trop rapide
|
||||
|
||||
✅ Bon
|
||||
duration: Duration(milliseconds: 200) // Standard
|
||||
duration: Duration(milliseconds: 300) // Pour layouts
|
||||
```
|
||||
|
||||
#### 4. Scale sur Hover
|
||||
|
||||
```dart
|
||||
❌ Mauvais - Provoque layout shift
|
||||
Transform.scale(
|
||||
scale: _isHovered ? 1.02 : 1.0,
|
||||
child: Card(),
|
||||
)
|
||||
|
||||
✅ Bon - Utilise color/shadow
|
||||
AnimatedContainer(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _isHovered ? AppColors.primary : AppColors.border,
|
||||
width: _isHovered ? 2 : 1,
|
||||
),
|
||||
boxShadow: _isHovered ? [/* glow */] : null,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
#### 5. Cursor Pointer Manquant
|
||||
|
||||
```dart
|
||||
❌ Mauvais
|
||||
GestureDetector(
|
||||
onTap: () => /* ... */,
|
||||
child: Card(), // Pas de cursor!
|
||||
)
|
||||
|
||||
✅ Bon
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => /* ... */,
|
||||
child: Card(),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
#### 6. URLs HTTP en Production
|
||||
|
||||
```dart
|
||||
❌ Mauvais
|
||||
static const String baseUrl = 'http://api.example.com';
|
||||
|
||||
✅ Bon
|
||||
static const String baseUrl = 'https://api.example.com';
|
||||
```
|
||||
|
||||
#### 7. Debug Logging en Production
|
||||
|
||||
```dart
|
||||
❌ Mauvais
|
||||
print('Debug info');
|
||||
print(userToken); // Expose des données sensibles!
|
||||
|
||||
✅ Bon
|
||||
if (kDebugMode) {
|
||||
debugPrint('Debug info');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Glossaire
|
||||
|
||||
### Terms
|
||||
|
||||
- **Néon**: Couleurs vives, lumineuses (cyan, violet, rose)
|
||||
- **Glow**: Ombre portée lumineuse autour des éléments
|
||||
- **Skeleton**: Placeholder animé pendant le chargement
|
||||
- **Hover**: État quand la souris passe sur un élément
|
||||
- **CTA**: Call-to-Action (bouton principal)
|
||||
- **WCAG AA**: Standard d'accessibilité web (contrast minimum 4.5:1)
|
||||
|
||||
### Color Names
|
||||
|
||||
- **Cyan**: Primary accent color (#00F0FF)
|
||||
- **Violet**: Secondary accent (#BF00FF)
|
||||
- **Rose**: Accent color (#FF006E)
|
||||
- **Surface**: Elevated backgrounds (#151932)
|
||||
- **Muted**: Disabled text (#6B7280)
|
||||
|
||||
---
|
||||
|
||||
## Checklist Rapide
|
||||
|
||||
### Avant de Committer
|
||||
|
||||
- [ ] Pas d'emojis comme icônes
|
||||
- [ ] Tous les éléments cliquables ont `cursor: pointer`
|
||||
- [ ] Hover states avec transitions 200ms
|
||||
- [ ] Contrast minimum 4.5:1 pour text
|
||||
- [ ] Focus states visibles sur inputs
|
||||
- [ ] Animations respectent `prefers-reduced-motion`
|
||||
- [ ] Images utilisent `CachedNetworkImage`
|
||||
- [ ] Pas de scale transforms sur hover
|
||||
- [ ] URLs en HTTPS (pas HTTP)
|
||||
- [ ] Pas de `print()` en production (utiliser `debugPrint`)
|
||||
|
||||
### Tests
|
||||
|
||||
- [ ] Test à 375px (mobile)
|
||||
- [ ] Test à 768px (tablet)
|
||||
- [ ] Test à 1024px (desktop)
|
||||
- [ ] Test à 1440px (wide)
|
||||
- [ ] Test hover states sur desktop
|
||||
- [ ] Test animations avec reduced-motion
|
||||
- [ ] Test contrast avec color blindness simulator
|
||||
- [ ] Test touch targets sur mobile
|
||||
|
||||
---
|
||||
|
||||
## Ressources
|
||||
|
||||
### Documentation
|
||||
|
||||
- **Design System Master**: `design-system/MASTER.md`
|
||||
- **Page Overrides**: `design-system/pages/*.md`
|
||||
- **Implementation Guide**: `DESIGN_IMPLEMENTATION_GUIDE.md`
|
||||
|
||||
### Outils
|
||||
|
||||
- **Contrast Checker**: https://webaim.org/resources/contrastchecker/
|
||||
- **Color Blindness Simulator**: https://www.toptal.com/designers/colorfilterweb/
|
||||
- **Google Fonts**: https://fonts.google.com/
|
||||
- **Lucide Icons**: https://lucide.dev/
|
||||
- **Heroicons**: https://heroicons.com/
|
||||
|
||||
### Packages Flutter
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
google_fonts: ^6.1.0
|
||||
shimmer: ^3.0.0
|
||||
cached_network_image: ^3.3.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^3.0.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 1.0 (2026-01-18)
|
||||
|
||||
- Initial style guide
|
||||
- Système de couleurs cyberpunk néon
|
||||
- Typography (Space Grotesk + Outfit)
|
||||
- Composants standards (buttons, cards, inputs)
|
||||
- Animations et transitions
|
||||
- Patterns et best practices
|
||||
- Anti-patterns documentés
|
||||
|
||||
---
|
||||
|
||||
**Mainteneurs:** AudiOhm Design Team
|
||||
**Contact:** design@audiohm.com
|
||||
**License:** MIT
|
||||
|
||||
---
|
||||
|
||||
*Ce guide est la source de vérité pour tous les aspects visuels et d'UX d'AudiOhm. Toute nouvelle implémentation doit suivre ces standards.*
|
||||
Reference in New Issue
Block a user