Niveau 20 — Le Responsive Design

Le Responsive Design

Créez des sites qui s'adaptent à toutes les tailles d'écran — du smartphone au grand écran. Media queries, design fluide, container queries : les techniques modernes du responsive.

1. Comprendre le Responsive Design

En une phrase : Le Responsive Design est une approche de développement web qui permet à un site de s'adapter automatiquement à la taille de l'écran de l'utilisateur, en utilisant des media queries, des layouts fluides et des images flexibles.

Le responsive design n'est plus optionnel — c'est une exigence fondamentale. Plus de 60% du trafic web provient de mobiles. Google pénalise les sites non responsive dans son référencement.

Les piliers du responsive

Pilier Technique Description
Media Queries @media Appliquer des styles selon la taille/propriétés de l'écran
Layout fluide %, vw, fr, min/max/clamp Des grilles et conteneurs qui s'étirent
Images flexibles max-width, object-fit Images qui ne débordent jamais
Typographie fluide clamp(), vw Tailles de police adaptatives

Les breakpoints (points de rupture)

Mobile < 480px
Tablette 480 – 768px
Desktop 768 – 1024px
Large 1024 – 1200px+
Mobile First vs Desktop First : L'approche Mobile First (définir les styles mobiles d'abord, puis enrichir avec min-width) est recommandée car elle produit un code plus léger et plus performant.

2. Syntaxe détaillée

2.1 Les Media Queries

Définition : Une media query permet d'appliquer des styles CSS uniquement lorsque certaines conditions sont remplies (taille de l'écran, orientation, résolution, etc.).
/* Syntaxe de base */
@media (condition) {
  /* Styles appliqués quand la condition est vraie */
}

/* Exemples */
@media (min-width: 768px) {
  .conteneur {
    padding: 2rem;
  }
}

@media (max-width: 480px) {
  .sidebar {
    display: none;
  }
}

Les opérateurs logiques

/* ET — les deux conditions doivent être vraies */
@media (min-width: 768px) and (max-width: 1024px) {
  /* Tablette uniquement */
}

/* OU — au moins une condition vraie */
@media (max-width: 480px), (orientation: portrait) {
  /* Mobile OU portrait */
}

/* NOT — négation */
@media not print {
  /* Tout sauf l'impression */
}

/* Imbrication (moderne) */
@media (min-width: 768px) {
  .container { width: 90%; }

  @media (min-width: 1024px) {
    .container { width: 80%; }
  }
}

Les media features courantes

Feature Description Exemple
width Largeur de la zone d'affichage (min-width: 768px)
height Hauteur de la zone d'affichage (max-height: 600px)
orientation Portrait ou landscape (orientation: portrait)
aspect-ratio Rapport largeur/hauteur (aspect-ratio: 16/9)
resolution Densité de pixels (min-resolution: 2dppx)
hover Support du survol (hover: hover)
pointer Précision du pointeur (pointer: coarse)
prefers-color-scheme Thème système (prefers-color-scheme: dark)
prefers-reduced-motion Réduction de mouvement (prefers-reduced-motion: reduce)
SYNTAXE DES MEDIA QUERIES
═══════════════════════════════════════════════

@media (type: feature: valeur) {
  /* styles */
}

Types :  all | screen | print

Features :
├── width / height / min-width / max-width
├── orientation (portrait | landscape)
├── aspect-ratio / min-aspect-ratio
├── resolution / min-resolution
├── hover (none | hover)
├── pointer (none | coarse | fine)
├── color-gamut
├── prefers-color-scheme
├── prefers-reduced-motion
└── ...

Opérateurs :
├── and    → ET logique
├── ,      → OU logique
└── not    → NÉGATION

Anatomie complète d'une media query

2.2 Mobile First vs Desktop First

Mobile First ✅
Base → styles mobile
min-width: 768px → tablette
min-width: 1024px → desktop
min-width: 1200px → large
Desktop First
Base → styles desktop
max-width: 1024px → tablette
max-width: 768px → mobile
max-width: 480px → petit mobile

Comparaison

Critère Mobile First Desktop First
Approche min-width (augmenter) max-width (diminuer)
Performance Meilleure (CSS de base = mobile) Moins bonne (CSS de base = desktop)
Code Plus propre, moins de overrides Plus de redondance
Maintenance Facile à étendre Peut devenir complexe
Adoption Recommandée par tous Légacy, migration progressive
/* ✅ MOBILE FIRST — Recommandé */
.card-grid {
  display: grid;
  grid-template-columns: 1fr;           /* Mobile : 1 colonne */
  gap: 1rem;
}

@media (min-width: 768px) {
  .card-grid {
    grid-template-columns: 1fr 1fr;     /* Tablette : 2 colonnes */
  }
}

@media (min-width: 1024px) {
  .card-grid {
    grid-template-columns: repeat(3, 1fr); /* Desktop : 3 colonnes */
  }
}

/* ❌ DESKTOP FIRST — À éviter */
.card-grid {
  grid-template-columns: repeat(3, 1fr); /* Desktop par défaut */
}

@media (max-width: 1023px) {
  .card-grid {
    grid-template-columns: 1fr 1fr;     /* Tablette : override */
  }
}

@media (max-width: 767px) {
  .card-grid {
    grid-template-columns: 1fr;          /* Mobile : override encore */
  }
}

2.3 Container Queries (Moderne)

Définition : Les container queries permettent de cibler des styles en fonction de la taille du conteneur parent plutôt que du viewport. Idéal pour les composants réutilisables qui s'adaptent à leur environnement.
/* Définir un conteneur */
.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

/* Styles conditionnels selon la taille du conteneur */
@container sidebar (min-width: 300px) {
  .widget {
    display: flex;
    gap: 1rem;
  }
}
A

Alice Martin

Développeuse Web Senior

B

Bernard Dupont

Designer UX/UI

Note : Les container queries sont supportées dans tous les navigateurs modernes (Chrome 105+, Firefox 110+, Safari 16+).

2.4 Design Fluide

Définition : Le design fluide utilise des unités relatives et des fonctions CSS (clamp(), min(), max()) pour créer des éléments qui s'adaptent progressivement à la taille de l'écran, sans media query.

Typographie fluide avec clamp()

/* clamp(min, préféré, max) */
h1 {
  font-size: clamp(1.5rem, 4vw, 3rem);
  /* Minimum 1.5rem, idéal 4vw, maximum 3rem */
}

p {
  font-size: clamp(0.875rem, 1.5vw, 1.05rem);
}

/* Échelle de typographie fluide */
:root {
  --fs-sm:   clamp(0.8rem, 0.17vw + 0.76rem, 0.89rem);
  --fs-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
  --fs-md:   clamp(1.25rem, 0.61vw + 1.1rem, 1.58rem);
  --fs-lg:   clamp(1.56rem, 1vw + 1.31rem, 2.11rem);
  --fs-xl:   clamp(1.95rem, 1.56vw + 1.56rem, 2.81rem);
  --fs-2xl:  clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
}
clamp(1.5rem, 4vw, 3rem)
Titre H1
clamp(1.2rem, 3vw, 2rem)
Titre H2
clamp(1rem, 2.5vw, 1.5rem)
Titre H3
clamp(0.85rem, 1.5vw, 1rem)
Texte de paragraphe qui s'adapte progressivement à la taille de l'écran sans cassure brutale.
clamp(0.75rem, 1vw, 0.875rem)
Texte petit pour notes et mentions légales

Espaces fluides

/* Espaces qui grandissent avec l'écran */
.section {
  padding: clamp(2rem, 5vw, 6rem) clamp(1rem, 3vw, 4rem);
}

.gap-fluide {
  gap: clamp(1rem, 2vw, 2rem);
}
clamp(0.5rem, 2vw, 2rem)
clamp(1rem, 3vw, 3rem)
clamp(1.5rem, 4vw, 4rem)
clamp(2rem, 5vw, 5rem)

Layouts fluides avec min(), max(), clamp()

/* Conteneur centré avec max-width */
.conteneur {
  width: min(90%, 1200px);
  margin: 0 auto;
}

/* Grille fluide */
.grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: clamp(1rem, 2vw, 2rem);
}

/* Sidebar fixe + contenu flexible */
.layout {
  display: grid;
  grid-template-columns: clamp(250px, 25vw, 300px) 1fr;
  gap: 2rem;
}

2.5 Images responsives

/* Image qui ne déborde jamais */
img {
  max-width: 100%;
  height: auto;
}

/* Image avec ratio fixe */
.image-cadre {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;     /* Remplit en gardant le ratio */
  object-fit: contain;   /* Contient sans déformer */
}

/* Image-set pour résolution */
.background {
  background-image: image-set(
    "photo@1x.jpg" 1x,
    "photo@2x.jpg" 2x
  );
}

/* Picture pour art direction */
<picture>
  <source media="(min-width: 1024px)" srcset="grand.jpg">
  <source media="(min-width: 768px)" srcset="moyen.jpg">
  <img src="petit.jpg" alt="Image responsive">
</picture>

2.6 Navigation responsive

/* Pattern hamburger classique */
.nav-links {
  display: none;            /* Caché sur mobile */
}

.nav-hamburger {
  display: block;           /* Visible sur mobile */
}

@media (min-width: 768px) {
  .nav-links {
    display: flex;          /* Visible sur desktop */
  }
  .nav-hamburger {
    display: none;          /* Caché sur desktop */
  }
}

/* Off-canvas menu */
@media (max-width: 767px) {
  .nav-menu {
    position: fixed;
    top: 0;
    left: -100%;           /* Caché à gauche */
    width: 280px;
    height: 100vh;
    transition: left 0.3s ease;
    z-index: 1000;
  }
  .nav-menu.active {
    left: 0;               /* Apparaît */
  }
}

2.7 Tables responsives

/* Conteneur scrollable */
.table-responsive {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* Alternative : cartes sur mobile */
@media (max-width: 767px) {
  table, thead, tbody, tr, td, th {
    display: block;
  }
  thead { display: none; }

  tr {
    margin-bottom: 1rem;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 0.75rem;
  }

  td {
    padding: 0.25rem 0;
    text-align: right;
  }

  td::before {
    content: attr(data-label);
    float: left;
    font-weight: 600;
    text-align: left;
  }
}

2.8 Patterns responsive

Mostly Fluid : Le pattern le plus courant. La grille reste fluide avec des media queries pour ajuster le nombre de colonnes.
/* Mostly Fluid */
.grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
  gap: 1rem;
}

/* Column Drop : les colonnes s'empilent sur mobile */
.layout {
  display: grid;
  grid-template-columns: 1fr;
}
@media (min-width: 768px) {
  .layout { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 1024px) {
  .layout { grid-template-columns: 1fr 2fr 1fr; }
}

/* Layout Shifter : le layout change radicalement */
.layout {
  display: grid;
  grid-template-columns: 1fr;
}
@media (min-width: 768px) {
  .layout {
    grid-template-columns: 1fr 2fr;
  }
}
@media (min-width: 1024px) {
  .layout {
    grid-template-columns: 200px 1fr 200px;
  }
}

3. Visualisation interactive

Breakpoints en action

📱
Mobile
< 480px
1 colonne, nav burger, touches larges
📋
Tablette
480 – 768px
2 colonnes, sidebar collapse
🖥️
Desktop
768 – 1024px
3 colonnes, nav complète
🖥️
Large
1200px+
Conteneur max, plus d'espace

Patterns responsive

Mostly Fluid
1
2
3
4
Column Drop
A
B
C
Layout Shifter
Nav
Contenu

Images responsives

object-fit: cover
Image cover
object-fit: contain
Image contain
max-width: 100%
Image max-width

4. Exemples progressifs

Exemple 1 : Grid responsive sans media query

/* Le grid s'adapte automatiquement */
.grille-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 1.5rem;
  padding: 1.5rem;
}

/* Résultat :
   - > 1180px : 4 colonnes
   - 880-1180px : 3 colonnes
   - 580-880px : 2 colonnes
   - < 580px : 1 colonne */

Exemple 2 : Layout complet mobile-first

/* Base : mobile (1 colonne) */
.page {
  display: grid;
  grid-template-areas:
    "header"
    "main"
    "sidebar"
    "footer";
  grid-template-columns: 1fr;
  gap: 1rem;
  padding: 1rem;
}

/* Tablette : 2 colonnes */
@media (min-width: 768px) {
  .page {
    grid-template-areas:
      "header  header"
      "main    sidebar"
      "footer  footer";
    grid-template-columns: 1fr 250px;
    padding: 2rem;
  }
}

/* Desktop : sidebar plus large */
@media (min-width: 1024px) {
  .page {
    grid-template-columns: 1fr 300px;
    max-width: 1200px;
    margin: 0 auto;
  }
}

.header  { grid-area: header; }
.main    { grid-area: main; }
.sidebar { grid-area: sidebar; }
.footer  { grid-area: footer; }

Exemple 3 : Navbar responsive complète

<nav class="nav">
  <div class="nav-logo">Logo</div>
  <input type="checkbox" id="nav-toggle" class="nav-toggle-input">
  <label for="nav-toggle" class="nav-hamburger">☰</label>
  <ul class="nav-links">
    <li><a href="#">Accueil</a></li>
    <li><a href="#">Services</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>

/* Base mobile : menu caché */
.nav { display: flex; justify-content: space-between; align-items: center; }
.nav-links { display: none; }
.nav-hamburger { display: block; }

/* Desktop : menu visible */
@media (min-width: 768px) {
  .nav-links { display: flex; gap: 1.5rem; }
  .nav-hamburger { display: none; }
}

Exemple 4 : Carte responsive avec container query

/* Le composant s'adapte à son conteneur, pas au viewport */
.card-container {
  container-type: inline-size;
}

.card {
  display: flex;
  flex-direction: column;
  padding: 1rem;
}

.card img {
  width: 100%;
  aspect-ratio: 16/9;
  object-fit: cover;
  border-radius: 8px;
}

/* Quand le conteneur fait plus de 400px */
@container (min-width: 400px) {
  .card {
    flex-direction: row;
    gap: 1rem;
  }

  .card img {
    width: 200px;
    aspect-ratio: auto;
  }
}

5. Erreurs fréquentes

Erreur 1 : Oublier le viewport meta tag
<!-- ❌ Sans ce tag, les media queries ne marchent pas sur mobile -->
<head>
  <title>Mon site</title>
</head>

<!-- ✅ Toujours inclure ce tag -->
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Mon site</title>
</head>
Erreur 2 : Utiliser des pixels fixes pour les widths
/* ❌ Ne s'adapte pas */
.conteneur { width: 960px; }
.colonne { width: 300px; }

/* ✅ Utilise des unités relatives */
.conteneur { width: min(90%, 1200px); }
.colonne { width: 100%; }
Erreur 3 : Desktop First avec trop de overrides
/* ❌ 3 niveaux d'override */
.grille { grid-template-columns: 4fr 1fr; }
@media (max-width: 1023px) { .grille { grid-template-columns: 1fr 250px; } }
@media (max-width: 767px)  { .grille { grid-template-columns: 1fr; } }

/* ✅ Mobile First : le code de base est déjà mobile */
.grille { grid-template-columns: 1fr; }
@media (min-width: 768px)  { .grille { grid-template-columns: 1fr 250px; } }
@media (min-width: 1024px) { .grille { grid-template-columns: 4fr 1fr; } }
Erreur 4 : cacher du contenu important sur mobile
/* ❌ Cacher le contenu vital */
@media (max-width: 767px) {
  .prix { display: none; }      /* Le prix est caché ! */
  .formulaire { display: none; } /* Pas de conversion ! */
}

/* ✅ Cacher uniquement le décoratif */
@media (max-width: 767px) {
  .decoratif { display: none; }
  .prix { font-size: 1.5rem; }  /* Plus visible sur mobile */
}
Erreur 5 : Breakpoints basés sur des appareils spécifiques
/* ❌ Fragile et incomplet */
@media (max-width: 375px) { /* iPhone SE */ }
@media (max-width: 414px) { /* iPhone XR */ }
@media (max-width: 360px) { /* Galaxy S5 */ }

/* ✅ Basés sur le contenu, pas sur les appareils */
@media (max-width: 480px) { /* Petit écran */ }
@media (max-width: 768px) { /* Tablette */ }
@media (max-width: 1024px) { /* Desktop */ }
Erreur 6 : Touch targets trop petites
/* ❌ Liens et boutons trop petits sur mobile */
.nav-link {
  padding: 4px 8px;    /* Trop petit pour un doigt */
  font-size: 0.7rem;
}

/* ✅ Taille minimale de 44x44px pour le tactile */
.nav-link {
  padding: 12px 16px;
  min-height: 44px;
  display: flex;
  align-items: center;
}

6. Bonnes pratiques

Système de breakpoints cohérent :
/* Définir les breakpoints dans des variables */
:root {
  --bp-sm: 576px;
  --bp-md: 768px;
  --bp-lg: 1024px;
  --bp-xl: 1200px;
}

/* Utiliser min-width avec ces variables */
@media (min-width: var(--bp-md)) {
  /* Note : les variables ne marchent pas directement dans
     les media queries, mais on peut utiliser des mixins
     ou des conventions de nommage */
Échelle de typographie fluide :
:root {
  --fs-xs:  clamp(0.7rem, 0.1vw + 0.68rem, 0.79rem);
  --fs-sm:  clamp(0.8rem, 0.17vw + 0.76rem, 0.89rem);
  --fs-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
  --fs-md:  clamp(1.25rem, 0.61vw + 1.1rem, 1.58rem);
  --fs-lg:  clamp(1.56rem, 1vw + 1.31rem, 2.11rem);
  --fs-xl:  clamp(1.95rem, 1.56vw + 1.56rem, 2.81rem);
  --fs-2xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
}
Responsive patterns les plus courants :
  • 📱 Grille de cardsauto-fit + minmax(min(280px, 100%), 1fr)
  • 📱 Sidebar layout → Grid 1fr1fr 250px
  • 📱 Hero sectionclamp() pour padding + typo
  • 📱 Navbar → Flex justify-content: space-between + hamburger
  • 📱 Imagesmax-width: 100% + object-fit
  • 📱 Tablesoverflow-x: auto ou transformation en cartes
Images responsive :
/* Toujours */
img { max-width: 100%; height: auto; }

/* Pour les images de fond */
.hero {
  background-image: url("hero.jpg");
  background-size: cover;
  background-position: center;
}

/* Pour garder un ratio fixe */
.image-cadre {
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* Pour servir différentes résolutions */
<picture>
  <source media="(min-width: 1024px)" srcset="large.webp" type="image/webp">
  <source media="(min-width: 768px)" srcset="medium.webp" type="image/webp">
  <img src="small.jpg" alt="Description" loading="lazy">
</picture>

7. Mini-défi

Défi 1 : Grille responsive

Créez une grille de cartes qui affiche 3 colonnes sur desktop, 2 sur tablette et 1 sur mobile, en utilisant Mobile First.

/* Votre code ici — Mobile First */
.grille {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

@media (min-width: 768px) {
  .grille {
    grid-template-columns: 1fr 1fr;
  }
}

@media (min-width: 1024px) {
  .grille {
    grid-template-columns: repeat(3, 1fr);
  }
}

Défi 2 : Typographie fluide

Créez une échelle de typographie fluide complète avec clamp() pour h1 à small, sans aucune media query.

Défi 3 : Navbar responsive

Créez une navbar avec un logo à gauche, des liens au centre et un bouton à droite. Sur mobile, les liens sont remplacés par un hamburger.

Défi 4 : Layout responsive complet

Créez un layout avec header, sidebar, contenu principal et footer. Sur mobile tout est en colonne, sur tablette sidebar + contenu, sur desktop sidebar fixe à gauche.

Défi 5 : Table responsive

Créez une table qui est scrollable horizontalement sur mobile OU qui se transforme en liste de cartes.

8. Projets pratiques

Projet 1 : Navbar responsive avec hamburger

Créez une navbar complète avec :

  • Logo à gauche, liens au centre, CTA à droite
  • Sur mobile (< 768px) : hamburger qui ouvre un menu overlay
  • Animation fluide du menu
  • Hover effects sur les liens
  • Accessible (focus, aria)

Projet 2 : Grille adaptative

Créez une grille qui s'adapte automatiquement :

  • Utiliser auto-fit et minmax()
  • Desktop : 3-4 colonnes
  • Tablette : 2 colonnes
  • Mobile : 1 colonne
  • Espacement fluide avec clamp()

Projet 3 : Cards responsives

Créez des cartes avec image, titre et description :

  • Images avec aspect-ratio et object-fit
  • Typographie fluide avec clamp()
  • Hover effects
  • Layout qui s'adapte avec container queries

Projet 4 : Hero section responsive

Créez une hero section :

  • Texte centré avec typographie fluide
  • Background image responsive
  • Padding fluide
  • Boutons avec taille tactile suffisante
  • Grille qui s'adapte

9. Révision

Récapitulatif des concepts clés

  • Menu adapté par taille d'écran
  • Concept Technique Usage
    Media queries @media Styles conditionnels selon l'écran
    Mobile First min-width Approche recommandée
    Container Queries @container Composants adaptatifs
    Typographie fluide clamp() Tailles de police adaptatives
    Layout fluide %, vw, fr, min/max Grilles et conteneurs flexibles
    Images responsives max-width, object-fit, picture Images qui s'adaptent
    Navigation responsive Hamburger, off-canvas

    Quiz rapide

    1. Quelle est la syntaxe d'une media query pour les écrans larges ?
    2. Quelle est la différence entre Mobile First et Desktop First ?
    3. Qu'est-ce que clamp(1rem, 3vw, 2rem) fait ?
    4. Comment créer une grille responsive sans media query ?
    5. Qu'est-ce qu'une container query ?
    6. Pourquoi le meta viewport est-il essentiel ?
    7. Quelle est la taille tactile minimale recommandée ?
    8. Comment gérer les images responsives ?
    9. Quel opérateur logique utiliser pour "tablette uniquement" ?
    10. Pourquoi faut-il éviter les breakpoints basés sur des appareils ?
    Réponses :
    1. @media (min-width: 1024px)
    2. Mobile First utilise min-width (augmenter), Desktop First utilise max-width (diminuer)
    3. Minimum 1rem, idéal 3vw, maximum 2rem — le texte s'adapte progressivement
    4. repeat(auto-fit, minmax(min(280px, 100%), 1fr))
    5. Un media query qui cible la taille du conteneur parent, pas du viewport
    6. Sans lui, le navigateur affiche la page en mode "desktop" sur mobile
    7. 44×44 pixels
    8. max-width: 100% + aspect-ratio + object-fit + <picture>
    9. and avec min-width et max-width : @media (min-width: 768px) and (max-width: 1023px)
    10. Les appareils changent constamment, les breakpoints de contenu sont universels

    Cheat sheet rapide

    /* Conteneur responsive */
    .conteneur { width: min(90%, 1200px); margin: 0 auto; }
    
    /* Grille responsive */
    .grille {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
      gap: clamp(1rem, 2vw, 2rem);
    }
    
    /* Typographie fluide */
    h1 { font-size: clamp(1.5rem, 4vw, 3rem); }
    p  { font-size: clamp(0.875rem, 1.5vw, 1rem); }
    
    /* Media queries — Mobile First */
    @media (min-width: 768px)  { /* Tablette */ }
    @media (min-width: 1024px) { /* Desktop */ }
    @media (min-width: 1200px) { /* Large */ }
    
    /* Image responsive */
    img { max-width: 100%; height: auto; }
    
    /* Image avec ratio fixe */
    .img { aspect-ratio: 16/9; object-fit: cover; }
    
    /* Container query */
    .card-wrapper { container-type: inline-size; }
    @container (min-width: 400px) { /* layout horizontal */ }
    
    /* Sidebar responsive */
    .layout {
      display: grid;
      grid-template-columns: 1fr;
    }
    @media (min-width: 768px) {
      .layout { grid-template-columns: 1fr 250px; }
    }
    
    /* Navbar responsive */
    @media (max-width: 767px) {
      .nav-links { display: none; }
      .hamburger { display: block; }
    }
    
    /* Table responsive */
    .table-wrap { overflow-x: auto; }