HTML5
Architecture Avancée

Maîtrisez HTML5 moderne ! Web Components, APIs avancées, PWA, et sémantique de pointe. Créez des applications web natives dignes d'une interface S.H.I.E.L.D.

🧩 Web Components
🚀 Progressive Web Apps
🎯 APIs Natives

🧠 HTML5 de Niveau Expert

Découvrez la différence entre HTML basique et HTML moderne de niveau professionnel. Nous allons créer une interface S.H.I.E.L.D. avec les techniques les plus avancées.

🔧 HTML Traditionnel

<div class="card">
  <div class="header">
    <h2>Titre</h2>
  </div>
  <div class="content">
    <p>Contenu simple</p>
    <div onclick="alert('Click')">
      Bouton
    </div>
  </div>
</div>

⚡ Fonctionnel mais non sémantique

🚀 HTML5 Moderne

<article itemscope itemtype="https://schema.org/NewsArticle">
  <header role="banner">
    <h1 itemprop="headline">Titre Sémantique</h1>
    <time datetime="2025-01-15" itemprop="datePublished">
      15 Janvier 2025
    </time>
  </header>
  
  <section itemprop="articleBody">
    <p>Contenu riche et accessible</p>
    <smart-button type="primary"
                 aria-label="Action principale">
      Action Intelligente
    </smart-button>
  </section>
</article>

🤯 Sémantique, accessible et intelligent !

🎯 Techniques HTML5 de Pointe

🧩

Web Components

customElements.define()

Custom Elements

this.attachShadow()

Shadow DOM

<template>

Templates HTML

📋

Sémantique Avancée

itemscope itemtype

Schema.org

role="application"

ARIA avancé

<main> <aside>

HTML5 structural

APIs Natives

navigator.serviceWorker

Service Workers

window.localStorage

Storage API

canvas.getContext('2d')

Canvas 2D/WebGL

🚀

Progressive Web Apps

manifest.json

Web App Manifest

sw.register()

Installation PWA

cache.addAll()

Cache Strategy

Performance Web

loading="lazy"

Lazy Loading

rel="preload"

Resource Hints

intersection-observer

APIs Performance

Accessibilité Avancée

aria-live="polite"

Live Regions

aria-describedby

Relations ARIA

tabindex="-1"

Focus Management

💪 Exercices HTML5 Avancés

Mettez en pratique les techniques HTML5 modernes avec ces 3 exercices progressifs. Créez des composants réutilisables et des interfaces intelligentes.

1

Exercice 1 : Web Component Avenger

Custom Elements + Shadow DOM + Template + Slot

🎯 Objectif

🦾

Iron Man

Tony Stark - Genius Inventor

Technology Flight

Créez un Web Component réutilisable pour afficher des cartes d'Avengers avec Shadow DOM pour l'encapsulation et des slots pour la flexibilité.

💻 Code à implémenter

HTML Template :

<template id="avenger-card-template">
  <style>
    /* TODO: Styles pour Shadow DOM */
    :host {
      /* TODO: Styles du composant hôte */
    }
    .card {
      /* TODO: Style de la carte */
    }
  </style>
  
  <div class="card">
    <!-- TODO: Structure avec slots -->
    <slot name="avatar"></slot>
    <slot name="content"></slot>
  </div>
</template>

JavaScript (à compléter) :

class AvengerCard extends HTMLElement {
  constructor() {
    super();
    // TODO: Attacher Shadow DOM
    // TODO: Cloner template
  }
  
  connectedCallback() {
    // TODO: Logique de connexion
  }
  
  static get observedAttributes() {
    // TODO: Attributs observés
    return ['name', 'power'];
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    // TODO: Réagir aux changements d'attributs
  }
}

// TODO: Définir le custom element
💡 Voir la solution complète
<template id="avenger-card-template">
  <style>
    :host {
      display: block;
      margin: 1rem 0;
    }
    
    .card {
      display: flex;
      align-items: center;
      padding: 1rem;
      border: 2px solid #E34F26;
      border-radius: 8px;
      background: rgba(227, 79, 38, 0.1);
      gap: 1rem;
      transition: transform 0.3s ease;
    }
    
    .card:hover {
      transform: translateY(-2px);
      box-shadow: 0 4px 20px rgba(227, 79, 38, 0.3);
    }
    
    ::slotted([slot="avatar"]) {
      width: 64px;
      height: 64px;
      border-radius: 50%;
      flex-shrink: 0;
    }
  </style>
  
  <div class="card">
    <slot name="avatar">
      <div class="default-avatar">🦸</div>
    </slot>
    <div class="content">
      <slot name="content">
        <h3>Avenger</h3>
        <p>Description par défaut</p>
      </slot>
    </div>
  </div>
</template>

<script>
class AvengerCard extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({mode: 'open'});
    const template = document.getElementById('avenger-card-template');
    shadow.appendChild(template.content.cloneNode(true));
  }
  
  connectedCallback() {
    console.log('Avenger card connectée au DOM');
  }
  
  static get observedAttributes() {
    return ['name', 'power', 'description'];
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'name') {
      this.updateContent();
    }
  }
  
  updateContent() {
    // Mise à jour du contenu basée sur les attributs
    const name = this.getAttribute('name') || 'Avenger';
    const power = this.getAttribute('power') || 'Unknown';
    
    this.shadowRoot.querySelector('slot[name="content"] h3').textContent = name;
    this.shadowRoot.querySelector('slot[name="content"] p').textContent = power;
  }
}

customElements.define('avenger-card', AvengerCard);
</script>
2

Exercice 2 : PWA S.H.I.E.L.D.

Service Worker + Web App Manifest + Cache Strategy

🎯 Objectif

🛡️

S.H.I.E.L.D. Command

Application installable hors-ligne

PWA Active

Créez une Progressive Web App complète avec installation, fonctionnement hors-ligne et synchronisation en arrière-plan pour les opérations S.H.I.E.L.D.

💻 Code à implémenter

manifest.json (à compléter) :

{
  "name": "S.H.I.E.L.D. Command Center",
  "short_name": "SHIELD",
  // TODO: Ajouter icônes, couleurs
  // TODO: Configuration display et orientation
  // TODO: URLs de démarrage
  "start_url": "/",
  "display": "standalone",
  "background_color": "#1e3c72",
  "theme_color": "#E34F26"
}

Service Worker (à compléter) :

const CACHE_NAME = 'shield-v1';
const STATIC_ASSETS = [
  '/',
  '/index.html',
  // TODO: Ajouter assets à cacher
];

self.addEventListener('install', event => {
  // TODO: Mettre en cache les assets
});

self.addEventListener('fetch', event => {
  // TODO: Stratégie Cache First/Network First
});
💡 Voir la solution complète avec Service Worker avancé
🔧 Service Worker Avancé - Architecture Complète
Architecture et Stratégies de Cache
// sw.js - Service Worker S.H.I.E.L.D. v2.0
const CACHE_VERSION = 'v2.1.0';
const CACHE_NAMES = {
  static: `shield-static-${CACHE_VERSION}`,
  dynamic: `shield-dynamic-${CACHE_VERSION}`,
  images: `shield-images-${CACHE_VERSION}`,
  api: `shield-api-${CACHE_VERSION}`
};

const STATIC_ASSETS = [
  '/',
  '/index.html',
  '/manifest.json',
  '/styles/main.css',
  '/styles/components.css',
  '/scripts/app.js',
  '/scripts/storage-manager.js',
  '/icons/icon-192.png',
  '/icons/icon-512.png',
  '/fonts/shield-ui.woff2',
  '/offline.html'  // Page hors-ligne de fallback
];

const API_ENDPOINTS = [
  '/api/agents',
  '/api/missions', 
  '/api/threats',
  '/api/status'
];

// 🚀 Installation - Phase de mise en cache initiale
self.addEventListener('install', event => {
  console.log('[SW] Installation démarrée');
  
  event.waitUntil(
    Promise.all([
      // Cache des assets statiques
      caches.open(CACHE_NAMES.static)
        .then(cache => {
          console.log('[SW] Mise en cache des assets statiques');
          return cache.addAll(STATIC_ASSETS);
        }),
      
      // Pré-cache des API critiques
      caches.open(CACHE_NAMES.api)
        .then(cache => {
          console.log('[SW] Pré-cache des APIs critiques');
          return Promise.allSettled(
            API_ENDPOINTS.map(url => 
              fetch(url)
                .then(response => cache.put(url, response))
                .catch(err => console.log(`[SW] Erreur pré-cache ${url}:`, err))
            )
          );
        })
    ])
  );
  
  // Force l'activation immédiate
  self.skipWaiting();
});

// ⚡ Activation - Nettoyage des anciens caches
self.addEventListener('activate', event => {
  console.log('[SW] Activation démarrée');
  
  event.waitUntil(
    Promise.all([
      // Nettoyage des anciens caches
      caches.keys().then(cacheNames => {
        const validCacheNames = Object.values(CACHE_NAMES);
        return Promise.all(
          cacheNames.map(cacheName => {
            if (!validCacheNames.includes(cacheName)) {
              console.log('[SW] Suppression ancien cache:', cacheName);
              return caches.delete(cacheName);
            }
          })
        );
      }),
      
      // Prendre le contrôle immédiatement
      self.clients.claim()
    ])
  );
});

// 🔄 Stratégies de Cache Intelligentes
self.addEventListener('fetch', event => {
  const request = event.request;
  const url = new URL(request.url);
  
  // Ignorer les requêtes non-HTTP
  if (!request.url.startsWith('http')) return;
  
  // Stratégie par type de ressource
  if (request.destination === 'image') {
    event.respondWith(handleImageRequest(request));
  } else if (url.pathname.startsWith('/api/')) {
    event.respondWith(handleApiRequest(request));
  } else if (request.destination === 'document') {
    event.respondWith(handleDocumentRequest(request));
  } else if (STATIC_ASSETS.includes(url.pathname)) {
    event.respondWith(handleStaticRequest(request));
  } else {
    event.respondWith(handleDynamicRequest(request));
  }
});

// 🖼️ Cache First pour les images
async function handleImageRequest(request) {
  try {
    const cachedResponse = await caches.match(request);
    if (cachedResponse) {
      return cachedResponse;
    }
    
    const networkResponse = await fetch(request);
    const cache = await caches.open(CACHE_NAMES.images);
    cache.put(request, networkResponse.clone());
    return networkResponse;
  } catch (error) {
    console.log('[SW] Erreur image:', error);
    // Retourner une image placeholder par défaut
    return caches.match('/icons/placeholder.png');
  }
}

// 🔄 Stale While Revalidate pour les APIs
async function handleApiRequest(request) {
  const cache = await caches.open(CACHE_NAMES.api);
  const cachedResponse = await cache.match(request);
  
  // Requête réseau en parallèle
  const networkPromise = fetch(request)
    .then(response => {
      if (response.ok) {
        cache.put(request, response.clone());
      }
      return response;
    })
    .catch(error => {
      console.log('[SW] API hors-ligne:', request.url);
      return null;
    });
  
  // Retourner le cache immédiatement si disponible
  return cachedResponse || networkPromise || 
    new Response(JSON.stringify({
      error: 'Service indisponible',
      offline: true,
      timestamp: Date.now()
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
}

// 📄 Network First pour les documents
async function handleDocumentRequest(request) {
  try {
    const networkResponse = await fetch(request);
    return networkResponse;
  } catch (error) {
    console.log('[SW] Document hors-ligne, fallback vers cache');
    const cachedResponse = await caches.match(request);
    return cachedResponse || caches.match('/offline.html');
  }
}

// ⚡ Cache First pour les assets statiques
async function handleStaticRequest(request) {
  const cachedResponse = await caches.match(request);
  if (cachedResponse) {
    return cachedResponse;
  }
  
  try {
    const networkResponse = await fetch(request);
    const cache = await caches.open(CACHE_NAMES.static);
    cache.put(request, networkResponse.clone());
    return networkResponse;
  } catch (error) {
    console.log('[SW] Asset statique indisponible:', request.url);
    throw error;
  }
}

// 🌐 Network First pour le contenu dynamique
async function handleDynamicRequest(request) {
  try {
    const networkResponse = await fetch(request);
    const cache = await caches.open(CACHE_NAMES.dynamic);
    cache.put(request, networkResponse.clone());
    return networkResponse;
  } catch (error) {
    const cachedResponse = await caches.match(request);
    return cachedResponse || caches.match('/offline.html');
  }
}

// 📡 Background Sync pour les données critiques
self.addEventListener('sync', event => {
  console.log('[SW] Background Sync:', event.tag);
  
  if (event.tag === 'sync-mission-data') {
    event.waitUntil(syncMissionData());
  } else if (event.tag === 'sync-agent-status') {
    event.waitUntil(syncAgentStatus());
  }
});

async function syncMissionData() {
  try {
    // Récupérer les données en attente depuis IndexedDB
    const pendingData = await getStoredData('pending-missions');
    
    for (const mission of pendingData) {
      const response = await fetch('/api/missions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(mission)
      });
      
      if (response.ok) {
        await removeStoredData('pending-missions', mission.id);
        console.log('[SW] Mission synchronisée:', mission.id);
      }
    }
  } catch (error) {
    console.log('[SW] Erreur sync missions:', error);
  }
}

// 🔔 Push Notifications pour les alertes
self.addEventListener('push', event => {
  console.log('[SW] Push reçu:', event);
  
  const options = {
    body: 'Nouvelle alerte de sécurité',
    icon: '/icons/icon-192.png',
    badge: '/icons/badge.png',
    vibrate: [200, 100, 200],
    data: {
      url: '/alerts',
      timestamp: Date.now()
    },
    actions: [
      {
        action: 'view',
        title: 'Voir l\'alerte',
        icon: '/icons/view.png'
      },
      {
        action: 'dismiss',
        title: 'Ignorer',
        icon: '/icons/dismiss.png'
      }
    ]
  };
  
  if (event.data) {
    const payload = event.data.json();
    options.body = payload.message;
    options.data.url = payload.url;
  }
  
  event.waitUntil(
    self.registration.showNotification('S.H.I.E.L.D. Alert', options)
  );
});

// Gestion des clics sur notifications
self.addEventListener('notificationclick', event => {
  event.notification.close();
  
  const action = event.action;
  const url = event.notification.data.url;
  
  if (action === 'view' || !action) {
    event.waitUntil(
      clients.openWindow(url)
    );
  }
  // L'action 'dismiss' ferme juste la notification
});

// 🔧 Fonctions utilitaires pour IndexedDB
async function getStoredData(storeName) {
  // Implémentation simplifiée - voir section localStorage avancée
  return [];
}

async function removeStoredData(storeName, id) {
  // Implémentation simplifiée - voir section localStorage avancée
  return true;
}
Intégration Client Avancée
// app.js - Gestion Service Worker côté client
class ServiceWorkerManager {
  constructor() {
    this.registration = null;
    this.isOnline = navigator.onLine;
    this.initServiceWorker();
    this.setupEventListeners();
  }
  
  async initServiceWorker() {
    if ('serviceWorker' in navigator) {
      try {
        this.registration = await navigator.serviceWorker.register('/sw.js', {
          scope: '/'
        });
        
        console.log('Service Worker enregistré:', this.registration.scope);
        
        // Écouter les mises à jour
        this.registration.addEventListener('updatefound', () => {
          const newWorker = this.registration.installing;
          newWorker.addEventListener('statechange', () => {
            if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
              this.showUpdateAvailable();
            }
          });
        });
        
      } catch (error) {
        console.error('Erreur Service Worker:', error);
      }
    }
  }
  
  setupEventListeners() {
    // Détecter les changements de connexion
    window.addEventListener('online', () => {
      this.isOnline = true;
      this.showConnectionStatus('En ligne');
      this.syncPendingData();
    });
    
    window.addEventListener('offline', () => {
      this.isOnline = false;
      this.showConnectionStatus('Mode hors-ligne');
    });
    
    // Écouter les messages du Service Worker
    navigator.serviceWorker.addEventListener('message', event => {
      const { type, data } = event.data;
      
      switch (type) {
        case 'CACHE_UPDATED':
          this.showCacheUpdateNotification(data);
          break;
        case 'SYNC_COMPLETED':
          this.showSyncCompletedNotification();
          break;
      }
    });
  }
  
  showUpdateAvailable() {
    const notification = document.createElement('div');
    notification.className = 'update-notification';
    notification.innerHTML = `
      

Nouvelle version disponible!

`; document.body.appendChild(notification); } async updateServiceWorker() { if (this.registration && this.registration.waiting) { this.registration.waiting.postMessage({ type: 'SKIP_WAITING' }); window.location.reload(); } } async syncPendingData() { if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) { try { await this.registration.sync.register('sync-mission-data'); await this.registration.sync.register('sync-agent-status'); console.log('Background Sync programmé'); } catch (error) { console.log('Background Sync non supporté, sync immédiat'); this.performImmediateSync(); } } } showConnectionStatus(status) { const statusEl = document.getElementById('connection-status'); if (statusEl) { statusEl.textContent = status; statusEl.className = this.isOnline ? 'text-green-500' : 'text-orange-500'; } } } // Initialiser le gestionnaire const swManager = new ServiceWorkerManager();
💾 localStorage Avancé - Gestion des Données
Architecture de Stockage Intelligente
// storage-manager.js - Gestionnaire de stockage S.H.I.E.L.D.
class StorageManager {
  constructor() {
    this.storageQuota = 0;
    this.storageUsage = 0;
    this.compressionEnabled = true;
    this.encryptionKey = this.generateEncryptionKey();
    
    this.initStorage();
    this.monitorQuota();
  }
  
  async initStorage() {
    // Vérifier les capacités de stockage
    if ('storage' in navigator && 'estimate' in navigator.storage) {
      const estimate = await navigator.storage.estimate();
      this.storageQuota = estimate.quota;
      this.storageUsage = estimate.usage;
      
      console.log(`[Storage] Quota: ${this.formatBytes(this.storageQuota)}`);
      console.log(`[Storage] Utilisé: ${this.formatBytes(this.storageUsage)}`);
    }
    
    // Initialiser IndexedDB pour les données complexes
    this.initIndexedDB();
    
    // Migration depuis localStorage si nécessaire
    this.migrateFromLocalStorage();
  }
  
  // 🗄️ localStorage Optimisé pour les données simples
  setItem(key, value, options = {}) {
    try {
      const data = {
        value: value,
        timestamp: Date.now(),
        expiry: options.expiry || null,
        compressed: false,
        encrypted: options.encrypt || false
      };
      
      // Compression pour les grandes données
      if (JSON.stringify(data).length > 1024 && this.compressionEnabled) {
        data.value = this.compress(JSON.stringify(data.value));
        data.compressed = true;
      }
      
      // Chiffrement pour les données sensibles
      if (options.encrypt) {
        data.value = this.encrypt(JSON.stringify(data.value));
      }
      
      localStorage.setItem(key, JSON.stringify(data));
      
      // Mettre à jour les métriques
      this.updateStorageMetrics();
      
      return true;
    } catch (error) {
      console.error('[Storage] Erreur setItem:', error);
      
      // Gestion du quota dépassé
      if (error.name === 'QuotaExceededError') {
        this.handleQuotaExceeded(key, value);
      }
      return false;
    }
  }
  
  getItem(key) {
    try {
      const item = localStorage.getItem(key);
      if (!item) return null;
      
      const data = JSON.parse(item);
      
      // Vérifier l'expiration
      if (data.expiry && Date.now() > data.expiry) {
        this.removeItem(key);
        return null;
      }
      
      let value = data.value;
      
      // Décompression si nécessaire
      if (data.compressed) {
        value = JSON.parse(this.decompress(value));
      }
      
      // Déchiffrement si nécessaire
      if (data.encrypted) {
        value = JSON.parse(this.decrypt(value));
      }
      
      return value;
    } catch (error) {
      console.error('[Storage] Erreur getItem:', error);
      return null;
    }
  }
  
  removeItem(key) {
    localStorage.removeItem(key);
    this.updateStorageMetrics();
  }
  
  // 📊 Gestion intelligente du cache
  handleQuotaExceeded(key, value) {
    console.log('[Storage] Quota dépassé, nettoyage automatique');
    
    // Stratégie LRU (Least Recently Used)
    const items = [];
    for (let i = 0; i < localStorage.length; i++) {
      const itemKey = localStorage.key(i);
      const item = localStorage.getItem(itemKey);
      
      try {
        const data = JSON.parse(item);
        items.push({
          key: itemKey,
          timestamp: data.timestamp,
          size: item.length
        });
      } catch (e) {
        // Élément corrompu, le supprimer
        localStorage.removeItem(itemKey);
      }
    }
    
    // Trier par timestamp (plus ancien en premier)
    items.sort((a, b) => a.timestamp - b.timestamp);
    
    // Supprimer les éléments les plus anciens jusqu'à libérer 25% de l'espace
    let freedSpace = 0;
    const targetSpace = this.getLocalStorageSize() * 0.25;
    
    for (const item of items) {
      if (freedSpace >= targetSpace) break;
      
      localStorage.removeItem(item.key);
      freedSpace += item.size;
      console.log(`[Storage] Supprimé: ${item.key} (${this.formatBytes(item.size)})`);
    }
    
    // Réessayer l'opération initiale
    this.setItem(key, value);
  }
  
  // 💾 IndexedDB pour les données complexes
  async initIndexedDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open('ShieldDB', 2);
      
      request.onerror = () => reject(request.error);
      
      request.onsuccess = () => {
        this.db = request.result;
        resolve(this.db);
      };
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        
        // Store pour les missions
        if (!db.objectStoreNames.contains('missions')) {
          const missionStore = db.createObjectStore('missions', { keyPath: 'id' });
          missionStore.createIndex('status', 'status', { unique: false });
          missionStore.createIndex('priority', 'priority', { unique: false });
          missionStore.createIndex('timestamp', 'timestamp', { unique: false });
        }
        
        // Store pour les agents
        if (!db.objectStoreNames.contains('agents')) {
          const agentStore = db.createObjectStore('agents', { keyPath: 'id' });
          agentStore.createIndex('status', 'status', { unique: false });
          agentStore.createIndex('clearanceLevel', 'clearanceLevel', { unique: false });
        }
        
        // Store pour les données en attente de sync
        if (!db.objectStoreNames.contains('pending-sync')) {
          const syncStore = db.createObjectStore('pending-sync', { 
            keyPath: 'id',
            autoIncrement: true 
          });
          syncStore.createIndex('type', 'type', { unique: false });
          syncStore.createIndex('timestamp', 'timestamp', { unique: false });
        }
        
        // Store pour le cache des images
        if (!db.objectStoreNames.contains('image-cache')) {
          const imageStore = db.createObjectStore('image-cache', { keyPath: 'url' });
          imageStore.createIndex('timestamp', 'timestamp', { unique: false });
        }
      };
    });
  }
  
  // 📝 Opérations IndexedDB avancées
  async saveMission(mission) {
    const transaction = this.db.transaction(['missions'], 'readwrite');
    const store = transaction.objectStore('missions');
    
    // Ajouter timestamp et métadonnées
    mission.timestamp = Date.now();
    mission.lastModified = Date.now();
    mission.syncStatus = navigator.onLine ? 'synced' : 'pending';
    
    return new Promise((resolve, reject) => {
      const request = store.put(mission);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }
  
  async getMissions(filters = {}) {
    const transaction = this.db.transaction(['missions'], 'readonly');
    const store = transaction.objectStore('missions');
    
    return new Promise((resolve, reject) => {
      const request = store.getAll();
      
      request.onsuccess = () => {
        let missions = request.result;
        
        // Appliquer les filtres
        if (filters.status) {
          missions = missions.filter(m => m.status === filters.status);
        }
        if (filters.priority) {
          missions = missions.filter(m => m.priority >= filters.priority);
        }
        
        // Trier par timestamp décroissant
        missions.sort((a, b) => b.timestamp - a.timestamp);
        
        resolve(missions);
      };
      
      request.onerror = () => reject(request.error);
    });
  }
  
  // 🔐 Chiffrement simple des données sensibles
  encrypt(data) {
    // Implémentation simple XOR pour la démonstration
    // En production, utiliser Web Crypto API
    let encrypted = '';
    for (let i = 0; i < data.length; i++) {
      encrypted += String.fromCharCode(
        data.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length)
      );
    }
    return btoa(encrypted);
  }
  
  decrypt(encryptedData) {
    const encrypted = atob(encryptedData);
    let decrypted = '';
    for (let i = 0; i < encrypted.length; i++) {
      decrypted += String.fromCharCode(
        encrypted.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length)
      );
    }
    return decrypted;
  }
  
  generateEncryptionKey() {
    // Générer une clé basée sur des caractéristiques du navigateur
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    ctx.textBaseline = 'top';
    ctx.font = '14px Arial';
    ctx.fillText('S.H.I.E.L.D. Security', 2, 2);
    return canvas.toDataURL().slice(-16);
  }
  
  // 📊 Compression simple
  compress(str) {
    // Compression RLE simplifiée pour la démonstration
    return str.replace(/(.)\1+/g, (match, char) => char + match.length);
  }
  
  decompress(str) {
    // Décompression RLE
    return str.replace(/(.)\d+/g, (match, char) => {
      const count = parseInt(match.slice(1));
      return char.repeat(count);
    });
  }
  
  // 📈 Métriques et monitoring
  updateStorageMetrics() {
    const usage = this.getLocalStorageSize();
    const percentage = (usage / (5 * 1024 * 1024)) * 100; // 5MB typique
    
    // Émettre un événement pour l'UI
    window.dispatchEvent(new CustomEvent('storage-update', {
      detail: {
        usage: usage,
        percentage: percentage,
        formatted: this.formatBytes(usage)
      }
    }));
    
    // Alerter si l'usage dépasse 80%
    if (percentage > 80) {
      console.warn('[Storage] Utilisation élevée:', percentage.toFixed(1) + '%');
    }
  }
  
  getLocalStorageSize() {
    let total = 0;
    for (let key in localStorage) {
      if (localStorage.hasOwnProperty(key)) {
        total += localStorage[key].length;
      }
    }
    return total;
  }
  
  formatBytes(bytes) {
    const units = ['B', 'KB', 'MB', 'GB'];
    let size = bytes;
    let unitIndex = 0;
    
    while (size >= 1024 && unitIndex < units.length - 1) {
      size /= 1024;
      unitIndex++;
    }
    
    return `${size.toFixed(1)} ${units[unitIndex]}`;
  }
  
  async monitorQuota() {
    if ('storage' in navigator && 'estimate' in navigator.storage) {
      const estimate = await navigator.storage.estimate();
      const usagePercentage = (estimate.usage / estimate.quota) * 100;
      
      console.log(`[Storage] Utilisation totale: ${usagePercentage.toFixed(1)}%`);
      
      // Demander le stockage persistant si utilisation élevée
      if (usagePercentage > 70 && 'persist' in navigator.storage) {
        const persistent = await navigator.storage.persist();
        console.log('[Storage] Persistance:', persistent ? 'accordée' : 'refusée');
      }
    }
  }
}

// 🎯 Initialisation et utilisation
const storageManager = new StorageManager();

// Exemples d'utilisation
const shieldAPI = {
  // Sauvegarder une mission avec chiffrement
  async saveMission(mission) {
    mission.classified = true;
    await storageManager.saveMission(mission);
    
    // Sauvegarder aussi en localStorage pour accès rapide
    storageManager.setItem(`mission_${mission.id}`, {
      id: mission.id,
      title: mission.title,
      status: mission.status
    }, { encrypt: mission.classified });
  },
  
  // Récupérer les données hors-ligne
  async getOfflineData() {
    return {
      missions: await storageManager.getMissions(),
      agents: storageManager.getItem('agents') || [],
      lastSync: storageManager.getItem('last-sync')
    };
  },
  
  // Nettoyer les données expirées
  cleanupExpiredData() {
    const keys = Object.keys(localStorage);
    keys.forEach(key => {
      const item = storageManager.getItem(key);
      if (item === null) {
        console.log(`[Cleanup] Supprimé: ${key}`);
      }
    });
  }
};

// Event listeners pour l'UI
window.addEventListener('storage-update', (event) => {
  const { usage, percentage, formatted } = event.detail;
  const statusEl = document.getElementById('storage-status');
  if (statusEl) {
    statusEl.innerHTML = `
      
Stockage: ${formatted}
`; } });
Stratégies de Persistence Avancées
// Stratégies de stockage par type de données
const StorageStrategies = {
  
  // 🎯 Configuration utilisateur - localStorage avec backup
  userPreferences: {
    save: (preferences) => {
      storageManager.setItem('user-preferences', preferences);
      // Backup en IndexedDB
      storageManager.db.transaction(['user-data'], 'readwrite')
        .objectStore('user-data')
        .put({ id: 'preferences', data: preferences });
    },
    
    load: async () => {
      // Essayer localStorage d'abord (plus rapide)
      let prefs = storageManager.getItem('user-preferences');
      if (!prefs) {
        // Fallback vers IndexedDB
        const transaction = storageManager.db.transaction(['user-data'], 'readonly');
        const result = await transaction.objectStore('user-data').get('preferences');
        prefs = result?.data;
      }
      return prefs || getDefaultPreferences();
    }
  },
  
  // 📱 État de l'application - sessionStorage + localStorage
  appState: {
    save: (state) => {
      // Session courante
      sessionStorage.setItem('app-state', JSON.stringify(state));
      
      // Persistance entre sessions (état critique seulement)
      const persistentState = {
        currentMission: state.currentMission,
        userLocation: state.userLocation,
        securityLevel: state.securityLevel
      };
      storageManager.setItem('persistent-state', persistentState);
    },
    
    restore: () => {
      // État de session d'abord
      const sessionState = JSON.parse(sessionStorage.getItem('app-state') || '{}');
      // Compléter avec l'état persistant
      const persistentState = storageManager.getItem('persistent-state') || {};
      
      return { ...persistentState, ...sessionState };
    }
  },
  
  // 💾 Cache intelligent avec TTL
  smartCache: {
    set: (key, data, ttl = 3600000) => { // TTL par défaut: 1 heure
      storageManager.setItem(`cache_${key}`, data, {
        expiry: Date.now() + ttl
      });
    },
    
    get: (key) => {
      return storageManager.getItem(`cache_${key}`);
    },
    
    invalidate: (pattern) => {
      const keys = Object.keys(localStorage);
      keys.forEach(key => {
        if (key.startsWith('cache_') && key.includes(pattern)) {
          storageManager.removeItem(key);
        }
      });
    }
  },
  
  // 🔄 Queue de synchronisation
  syncQueue: {
    add: async (operation) => {
      const transaction = storageManager.db.transaction(['pending-sync'], 'readwrite');
      const store = transaction.objectStore('pending-sync');
      
      await new Promise((resolve, reject) => {
        const request = store.add({
          type: operation.type,
          data: operation.data,
          timestamp: Date.now(),
          retries: 0,
          maxRetries: 3
        });
        request.onsuccess = () => resolve();
        request.onerror = () => reject(request.error);
      });
    },
    
    process: async () => {
      const transaction = storageManager.db.transaction(['pending-sync'], 'readwrite');
      const store = transaction.objectStore('pending-sync');
      
      const operations = await new Promise((resolve, reject) => {
        const request = store.getAll();
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
      });
      
      for (const op of operations) {
        try {
          await this.executeOperation(op);
          // Supprimer après succès
          await store.delete(op.id);
        } catch (error) {
          op.retries++;
          if (op.retries >= op.maxRetries) {
            console.error('[Sync] Échec définitif:', op, error);
            await store.delete(op.id);
          } else {
            await store.put(op);
          }
        }
      }
    },
    
    executeOperation: async (operation) => {
      switch (operation.type) {
        case 'mission-update':
          await fetch('/api/missions/' + operation.data.id, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(operation.data)
          });
          break;
        case 'agent-status':
          await fetch('/api/agents/' + operation.data.id + '/status', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ status: operation.data.status })
          });
          break;
      }
    }
  }
};

// Utilisation des stratégies
class ShieldDataManager {
  constructor() {
    this.strategies = StorageStrategies;
    this.initializeDataLayer();
  }
  
  async initializeDataLayer() {
    // Restaurer l'état de l'application
    const appState = this.strategies.appState.restore();
    window.appState = appState;
    
    // Charger les préférences utilisateur
    const preferences = await this.strategies.userPreferences.load();
    this.applyUserPreferences(preferences);
    
    // Traiter la queue de synchronisation
    if (navigator.onLine) {
      await this.strategies.syncQueue.process();
    }
  }
  
  // API unifié pour les différents types de données
  async saveData(type, key, data, options = {}) {
    switch (type) {
      case 'cache':
        this.strategies.smartCache.set(key, data, options.ttl);
        break;
      case 'user-pref':
        this.strategies.userPreferences.save(data);
        break;
      case 'app-state':
        this.strategies.appState.save(data);
        break;
      case 'mission':
        await storageManager.saveMission(data);
        if (!navigator.onLine) {
          await this.strategies.syncQueue.add({
            type: 'mission-update',
            data: data
          });
        }
        break;
    }
  }
  
  async loadData(type, key, defaultValue = null) {
    switch (type) {
      case 'cache':
        return this.strategies.smartCache.get(key) || defaultValue;
      case 'user-pref':
        return await this.strategies.userPreferences.load();
      case 'app-state':
        return this.strategies.appState.restore();
      case 'missions':
        return await storageManager.getMissions();
      default:
        return storageManager.getItem(key) || defaultValue;
    }
  }
}

const dataManager = new ShieldDataManager();
📊 Interface de Monitoring Storage
Stockage: Initialisation...
localStorage
0 KB
IndexedDB
0 KB

manifest.json :

{
  "name": "S.H.I.E.L.D. Command Center",
  "short_name": "SHIELD",
  "description": "Interface de commandement S.H.I.E.L.D. avec capacités hors-ligne avancées",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#1e3c72",
  "theme_color": "#E34F26",
  "orientation": "portrait",
  "scope": "/",
  "lang": "fr",
  "dir": "ltr",
  "categories": ["security", "productivity", "utilities"],
  "icons": [
    {
      "src": "icons/icon-72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "icons/icon-96.png", 
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "icons/icon-128.png",
      "sizes": "128x128", 
      "type": "image/png"
    },
    {
      "src": "icons/icon-144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "icons/icon-152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable"
    },
    {
      "src": "icons/icon-384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "shortcuts": [
    {
      "name": "Missions actives",
      "url": "/missions?status=active",
      "description": "Voir les missions en cours"
    },
    {
      "name": "Alertes urgentes",
      "url": "/alerts?priority=high", 
      "description": "Alertes de sécurité prioritaires"
    },
    {
      "name": "Statut agents",
      "url": "/agents",
      "description": "État des agents sur le terrain"
    }
  ],
  "screenshots": [
    {
      "src": "screenshots/desktop-1280x720.png",
      "sizes": "1280x720",
      "type": "image/png",
      "form_factor": "wide",
      "label": "Interface principale desktop"
    },
    {
      "src": "screenshots/mobile-540x720.png", 
      "sizes": "540x720",
      "type": "image/png",
      "form_factor": "narrow",
      "label": "Interface mobile responsive"
    }
  ],
  "related_applications": [
    {
      "platform": "play",
      "url": "https://play.google.com/store/apps/details?id=com.shield.command",
      "id": "com.shield.command"
    }
  ]
}
3

Exercice 3 : Interface Holographique

Canvas 2D + WebGL + Interaction + Animation

🎯 Objectif

Votre navigateur ne supporte pas Canvas

Créez une interface holographique interactive utilisant Canvas 2D et WebGL. L'utilisateur peut interagir avec des éléments 3D et voir des données en temps réel.

💻 Code à implémenter

HTML Canvas :

<canvas id="hologram" 
        width="800" 
        height="600"
        aria-label="Interface holographique interactive">
  Interface holographique non supportée
</canvas>

JavaScript (à compléter) :

class HologramInterface {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.ctx = this.canvas.getContext('2d');
    // TODO: Initialiser WebGL si disponible
    // TODO: Configurer les événements de souris
  }
  
  render() {
    // TODO: Nettoyer le canvas
    // TODO: Dessiner les éléments holographiques
    // TODO: Animer les particules
    requestAnimationFrame(() => this.render());
  }
  
  handleMouseMove(event) {
    // TODO: Interaction avec la souris
  }
}

// TODO: Initialiser l'interface
💡 Voir la solution complète
class HologramInterface {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.ctx = this.canvas.getContext('2d');
    this.mouseX = 0;
    this.mouseY = 0;
    this.particles = [];
    this.time = 0;
    
    this.initParticles();
    this.bindEvents();
    this.render();
  }
  
  initParticles() {
    for (let i = 0; i < 50; i++) {
      this.particles.push({
        x: Math.random() * this.canvas.width,
        y: Math.random() * this.canvas.height,
        vx: (Math.random() - 0.5) * 2,
        vy: (Math.random() - 0.5) * 2,
        size: Math.random() * 3 + 1,
        color: `hsl(${120 + Math.random() * 60}, 70%, 50%)`
      });
    }
  }
  
  bindEvents() {
    this.canvas.addEventListener('mousemove', (e) => {
      const rect = this.canvas.getBoundingClientRect();
      this.mouseX = e.clientX - rect.left;
      this.mouseY = e.clientY - rect.top;
    });
    
    this.canvas.addEventListener('click', (e) => {
      this.createRipple(this.mouseX, this.mouseY);
    });
  }
  
  createRipple(x, y) {
    // Effet de ripple au clic
    for (let i = 0; i < 20; i++) {
      const angle = (i / 20) * Math.PI * 2;
      this.particles.push({
        x: x,
        y: y,
        vx: Math.cos(angle) * 5,
        vy: Math.sin(angle) * 5,
        size: 2,
        color: '#00FF94',
        life: 60
      });
    }
  }
  
  drawGrid() {
    this.ctx.strokeStyle = 'rgba(0, 217, 255, 0.2)';
    this.ctx.lineWidth = 1;
    
    const gridSize = 30;
    for (let x = 0; x < this.canvas.width; x += gridSize) {
      this.ctx.beginPath();
      this.ctx.moveTo(x, 0);
      this.ctx.lineTo(x, this.canvas.height);
      this.ctx.stroke();
    }
    
    for (let y = 0; y < this.canvas.height; y += gridSize) {
      this.ctx.beginPath();
      this.ctx.moveTo(0, y);
      this.ctx.lineTo(this.canvas.width, y);
      this.ctx.stroke();
    }
  }
  
  drawCursor() {
    const radius = 20;
    const pulseRadius = radius + Math.sin(this.time * 0.1) * 5;
    
    this.ctx.strokeStyle = '#00FF94';
    this.ctx.lineWidth = 2;
    this.ctx.beginPath();
    this.ctx.arc(this.mouseX, this.mouseY, pulseRadius, 0, Math.PI * 2);
    this.ctx.stroke();
    
    // Croix au centre
    this.ctx.beginPath();
    this.ctx.moveTo(this.mouseX - 10, this.mouseY);
    this.ctx.lineTo(this.mouseX + 10, this.mouseY);
    this.ctx.moveTo(this.mouseX, this.mouseY - 10);
    this.ctx.lineTo(this.mouseX, this.mouseY + 10);
    this.ctx.stroke();
  }
  
  updateParticles() {
    for (let i = this.particles.length - 1; i >= 0; i--) {
      const p = this.particles[i];
      
      p.x += p.vx;
      p.y += p.vy;
      
      // Rebond sur les bords
      if (p.x < 0 || p.x > this.canvas.width) p.vx *= -1;
      if (p.y < 0 || p.y > this.canvas.height) p.vy *= -1;
      
      // Attraction vers la souris
      const dx = this.mouseX - p.x;
      const dy = this.mouseY - p.y;
      const distance = Math.sqrt(dx * dx + dy * dy);
      
      if (distance < 100) {
        p.vx += dx * 0.0001;
        p.vy += dy * 0.0001;
      }
      
      // Vie limitée pour les ripples
      if (p.life !== undefined) {
        p.life--;
        if (p.life <= 0) {
          this.particles.splice(i, 1);
          continue;
        }
      }
      
      // Dessiner la particule
      this.ctx.fillStyle = p.color;
      this.ctx.beginPath();
      this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
      this.ctx.fill();
    }
  }
  
  drawHUD() {
    // Interface HUD style S.H.I.E.L.D.
    this.ctx.strokeStyle = '#00D9FF';
    this.ctx.fillStyle = '#00D9FF';
    this.ctx.font = '14px monospace';
    this.ctx.lineWidth = 2;
    
    // Coin supérieur gauche
    this.ctx.strokeRect(10, 10, 200, 80);
    this.ctx.fillText('S.H.I.E.L.D. SYSTEM', 20, 30);
    this.ctx.fillText(`PARTICLES: ${this.particles.length}`, 20, 50);
    this.ctx.fillText(`CURSOR: ${this.mouseX}, ${this.mouseY}`, 20, 70);
    
    // Coin supérieur droit
    const rightX = this.canvas.width - 150;
    this.ctx.strokeRect(rightX, 10, 140, 60);
    this.ctx.fillText('STATUS: ONLINE', rightX + 10, 30);
    this.ctx.fillText(`TIME: ${this.time}`, rightX + 10, 50);
  }
  
  render() {
    this.time++;
    
    // Nettoyer le canvas avec un effet de trail
    this.ctx.fillStyle = 'rgba(15, 20, 25, 0.1)';
    this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    
    this.drawGrid();
    this.updateParticles();
    this.drawCursor();
    this.drawHUD();
    
    requestAnimationFrame(() => this.render());
  }
}

// Initialiser l'interface quand le DOM est prêt
document.addEventListener('DOMContentLoaded', () => {
  new HologramInterface('hologram');
});

🛡️ S.H.I.E.L.D. Command PWA

Voici le résultat final : une Progressive Web App complète utilisant toutes les techniques HTML5 avancées. Interface de commandement S.H.I.E.L.D. installable avec fonctionnement hors-ligne.

🖥️ S.H.I.E.L.D. Command Center v3.0

PWA Ready
🕵️

Agent Status

Field Operations

Phil Coulson
Maria Hill
Nick Fury
⚠️

Threat Assessment

Global Security

Level 4 - High
Last Update: 2 minutes ago
🦸

Avengers

Response Team

🦾
Iron Man
🛡️
Captain America
Thor
🎯

Active Missions

Worldwide Operations

Operation: Insight
Active

Helicarrier surveillance protocol

Location: Washington D.C.
Project: Tahiti
Classified

Enhanced individual recovery

Location: [REDACTED]
⚙️

System Status

Infrastructure

Helicarrier 98.7%
Satellite Network 100%
Communications 87.2%
PWA Mode: Online
|
Service Worker: Active
|
Cache: 47 assets
"World Security is Our Mission"
📱

Installable

App native avec icône sur l'écran d'accueil

🔌

Offline First

Fonctionne sans connexion internet

🔔

Push Notifications

Alertes mission en temps réel

🚀

Performance

Chargement instantané avec le cache

🚀 Maîtrisez HTML5 Moderne 🚀

Vous venez de découvrir les techniques HTML5 les plus avancées ! Web Components, PWA, APIs natives - tout pour créer des applications web modernes.

🏆 Votre Parcours HTML5 Expert

Sémantique Web Components PWA APIs Natives Expert HTML5