Files
AudiOhm/archives/docs/STYLE_GUIDE.md
T
root 801e6a050b 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>
2026-01-20 09:56:39 +00:00

19 KiB

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
  2. Système de Design
  3. Typography
  4. Couleurs
  5. Espacing
  6. Composants
  7. Animations
  8. Patterns
  9. Best Practices
  10. 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

// 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

// 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

// 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

// 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

// 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

// 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

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

// 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)

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)

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

// Size standards
iconSizeSmall:  16px
iconSizeMedium: 20px
iconSizeLarge:  24px
iconSizeXL:     32px

// Touch targets
minTouchTarget: 44x44px  // Mobile
minClickTarget: 40x40px  // Desktop

Cards

Base Card

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)

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

// 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

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

// 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

// Easing curves
curveEaseOut:   Curves.easeOut        // Sortie fluide
curveEaseInOut: Curves.easeInOut      // Entrée-sortie fluide
curveBounce:    Curves.elasticOut     // Effet rebond (play button)

Transitions

Hover States

AnimatedContainer(
  duration: Duration(milliseconds: 200),
  curve: Curves.easeOut,
  decoration: BoxDecoration(
    // Changes to animate
  ),
)

Page Transitions

// 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

Shimmer.fromColors(
  baseColor: AppColors.surfaceVariant,
  highlightColor: AppColors.surfaceElevated,
  child: /* Skeleton content */,
)

Progress Indicator

CircularProgressIndicator(
  valueColor: AlwaysStoppedAnimation<Color>(AppColors.primary),
  backgroundColor: AppColors.surfaceVariant,
  strokeWidth: 3,
)

Patterns

Hover State Pattern

// 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

// Wrapper pour ajouter cursor pointer
MouseRegion(
  cursor: SystemMouseCursors.click,
  child: GestureDetector(
    onTap: () => /* action */,
    child: /* widget */,
  ),
)

Error Display Pattern

// 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

// 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

// Minimum sizes
mobileMinTapTarget:   44x44px
desktopMinClickTarget: 40x40px

Focus States

// Toujours montrer le focus
InputDecoration(
  focusedBorder: OutlineInputBorder(
    borderSide: BorderSide(color: AppColors.primary, width: 2),
  ),
)

2. Performance

Images

// 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

// 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

// 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

 Mauvais
Icon(Icons.emoji_events)  // N'utilisez pas d'emojis
Text('🎵 Music')

 Bon
Icon(Icons.music_note)
Icon(Icons.audiotrack)

2. Text Contrast Faible

 Mauvais
TextStyle(
  color: Color(0xFF6A7294),  // Trop sombre pour dark mode
)

 Bon
TextStyle(
  color: AppColors.textSecondary,  #9BA3B8 - Contrast 4.8:1
)

3. Transitions Instantanées

 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

 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

 Mauvais
GestureDetector(
  onTap: () => /* ... */,
  child: Card(),  // Pas de cursor!
)

 Bon
MouseRegion(
  cursor: SystemMouseCursors.click,
  child: GestureDetector(
    onTap: () => /* ... */,
    child: Card(),
  ),
)

6. URLs HTTP en Production

 Mauvais
static const String baseUrl = 'http://api.example.com';

 Bon
static const String baseUrl = 'https://api.example.com';

7. Debug Logging en Production

 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

Packages Flutter

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.