---
title: "Créer un thème"
weight: 35
description: "Guide complet pour graphistes et webdesigners : créer une charte graphique sur mesure pour la boutique OnePageBasket."
---

# Créer un thème

Cette page s'adresse aux graphistes et webdesigners qui veulent habiller une
boutique OnePageBasket aux couleurs d'une marque. Aucune connaissance de
Dolibarr n'est nécessaire : un thème se compose de fichiers HTML et CSS, et le
module se charge de fournir les données.

## Le principe en trois phrases

Un thème est un dossier posé dans `themes/`. Il ne contient **que ce que vous
voulez changer** : tout ce qui manque est automatiquement repris du thème
`default`. Vous pouvez donc livrer un thème d'un seul fichier CSS, ou un thème
qui redéfinit toutes les pages.

```
themes/
├── default/        <- la référence, toujours complète
├── InfraS/
├── lumen/
├── marketplace/
├── shopiplace/
├── chacha/
├── nordic/
└── macharte/       <- votre thème
```

Quand la boutique affiche la page produit et que `themes/macharte/product.blade.php`
n'existe pas, c'est `themes/default/product.blade.php` qui est utilisé, avec le
CSS de `macharte`. C'est ce repli qui rend les thèmes légers.

> **Le seul vrai piège du système.** Un gabarit que vous copiez est figé au jour
> de la copie : il ne recevra plus aucune évolution ultérieure de `default`. Plus
> vous copiez de gabarits, plus votre thème demandera de maintenance. Avant de
> copier un fichier, demandez-vous toujours si le besoin ne tient pas seulement
> au style : dans ce cas, le CSS suffit.

## Trois niveaux d'effort

| Niveau | Ce que vous livrez | Durée indicative | Maintenance |
|--------|--------------------|------------------|-------------|
| **Palette** | `input.css` seul | Une demi-journée | Quasi nulle |
| **Vitrine** | `input.css` + `app`, `header`, `footer`, `index`, `tools/productcard` | Deux à trois jours | Faible |
| **Complet** | Toutes les pages, y compris le portail client | Une à deux semaines | Élevée |

Les thèmes `lumen`, `marketplace`, `shopiplace` et `chacha` sont des thèmes de
niveau "vitrine" : entre 7 et 12 fichiers chacun. Ce sont les meilleurs
modèles à étudier avant de commencer.

## Ce qu'il vous faut sur votre poste

- Un accès aux fichiers du module (`custom/onepagebasket/` dans Dolibarr).
- **Node.js** avec `npx` disponible, pour compiler le CSS.
- Un éditeur de texte. Aucun autre outil n'est nécessaire.

Vérification rapide :

```bash
npx tailwindcss --help
```

## Étape 1 - Créer le dossier

```bash
mkdir -p themes/macharte/tools
```

Le nom du dossier est celui qui apparaîtra dans l'administration. Utilisez
uniquement des lettres, chiffres, tirets et soulignés : les autres noms sont
ignorés par le module.

## Étape 2 - Le fichier `input.css`

C'est le coeur du thème. Il définit la palette, les polices et les arrondis.
Voici un fichier de départ complet, prêt à être modifié :

```css
@import "tailwindcss" source(none);
@tailwind base;
@tailwind components;
@tailwind utilities;
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));

/*
 * Sources à scanner. Ce bloc est OBLIGATOIRE et ne doit pas être raccourci :
 * il liste les fichiers où Tailwind va chercher les classes utilisées.
 * Le thème hérite des gabarits de default/, donc leurs classes doivent être
 * compilées ici aussi, sinon la boutique s'affiche en HTML brut sur toutes
 * les pages que votre thème ne redéfinit pas.
 */
@source "./*.blade.php";
@source "./**/*.blade.php";
@source "../default/*.blade.php";
@source "../default/**/*.blade.php";
@source "../../public/**/*.{html,js,php}";
@source "../../langs/*/*.lang";

@plugin "daisyui" {
	themes: light --default, dark --prefersdark;
}

/* ============================================================
 * Palette - mode clair
 * ============================================================ */
[data-theme="light"],
:root:has(input.theme-controller[value=light]:checked),
:where(:root) {
	--color-primary: #B4794E;          /* couleur d'action principale */
	--color-primary-content: #ffffff;  /* texte posé sur la couleur primaire */
	--color-secondary: #17171A;
	--color-secondary-content: #ffffff;
	--color-accent: #9A6A3C;           /* survol, accents */
	--color-accent-content: #ffffff;
	--color-neutral: #17171A;
	--color-neutral-content: #F6F5F1;
	--color-base-100: #ffffff;         /* cartes, surfaces */
	--color-base-200: #F6F5F1;         /* fond de page */
	--color-base-300: #E7E5DE;         /* bordures, séparateurs */
	--color-base-content: #17171A;     /* couleur du texte */
	--color-info: #2F6FB0;
	--color-info-content: #ffffff;
	--color-success: #2E7D5B;
	--color-success-content: #ffffff;
	--color-warning: #B8860B;
	--color-warning-content: #ffffff;
	--color-error: #C0392B;            /* suppression, promotions */
	--color-error-content: #ffffff;
	--radius-box: 1rem;                /* arrondi des cartes */
	--radius-field: 0.625rem;          /* arrondi des champs et boutons */
	--radius-selector: 0.5rem;
}

/* ============================================================
 * Palette - mode sombre
 * ============================================================ */
[data-theme="dark"] {
	--color-primary: #C68B5E;
	--color-primary-content: #1A1208;
	--color-secondary: #EDEBE6;
	--color-secondary-content: #18181B;
	--color-accent: #C68B5E;
	--color-accent-content: #1A1208;
	--color-neutral: #EDEBE6;
	--color-neutral-content: #18181B;
	--color-base-100: #1B1B1F;
	--color-base-200: #141417;
	--color-base-300: #2A2A2F;
	--color-base-content: #EDEBE6;
	--color-info: #5B93D6;
	--color-success: #4CAF82;
	--color-warning: #D7A53B;
	--color-error: #E06B5E;
}

/* ============================================================
 * Typographie
 * ============================================================ */
body {
	font-family: 'Inter', system-ui, -apple-system, sans-serif;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}
```

Ces variables sont celles de DaisyUI : en les redéfinissant, vous recolorez
d'un coup tous les boutons, badges, alertes et cartes de la boutique, y compris
sur les pages que votre thème ne redéfinit pas. **C'est le meilleur rapport
effort / résultat du système.**

### Pourquoi `source(none)` est obligatoire

Sans cette option, Tailwind v4 scanne toute la racine du projet. Chaque thème
embarquerait alors les classes de tous les autres, et son CSS deviendrait
périmé dès qu'un thème voisin change. Le fichier compilé passerait aussi de
230 Ko à plusieurs mégaoctets.

Seul le thème `default` omet les deux lignes `../default` : il n'hérite de
personne.

### Les polices

**Aucune ressource distante n'est autorisée** : pas de Google Fonts, pas de CDN.
Les polices sont servies depuis le dossier `public/fonts/` de la boutique.

1. Déposez vos fichiers `.woff2` dans `public/fonts/`.
2. Déclarez-les dans `input.css` avec un chemin `./fonts/...` :

```css
@font-face {
	font-family: 'Fraunces';
	src: url('./fonts/Fraunces.woff2') format('woff2');
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
}
```

Le chemin commence par `./fonts/` et non `../../public/fonts/` : la feuille de
style est distribuée par un script situé à la racine publique de la boutique,
c'est donc de là que le navigateur résout les URL.

Choisissez des polices libres de droits pour la diffusion web (licence OFL par
exemple). Les polices déjà disponibles sont Inter, Fraunces, Lato, Montserrat,
Poppins.

## Étape 3 - Compiler le CSS

Ajoutez la cible de compilation dans `Makefile.dist`, à côté des autres :

```makefile
css-macharte: themes/macharte/*
	npx tailwindcss -i ./themes/macharte/input.css -o ./themes/macharte/index.css
```

Puis ajoutez `css-macharte` à la ligne `.PHONY` et à la cible groupée `css`.

Compilation :

```bash
make css-macharte     # votre thème seul
make css              # tous les thèmes
```

Le fichier produit, `themes/macharte/index.css`, doit être livré avec le thème :
c'est lui que la boutique sert aux visiteurs. **Après toute modification d'un
gabarit ou du `input.css`, recompilez.** Un gabarit modifié sans recompilation
affiche des classes CSS qui n'existent pas dans la feuille de style.

## Étape 4 - Activer le thème

Dans Dolibarr : **Accueil > Configuration > Modules > OnePageBasket > icône
engrenage > onglet Apparence**. Votre thème apparaît dans la liste déroulante
dès que le dossier existe.

Deux réglages voisins vous concernent :

| Réglage | Effet |
|---------|-------|
| Couleur principale du thème | Couleur configurable par le commerçant, lisible depuis les gabarits (voir plus bas) |
| Utiliser le favicon du thème | Sert le fichier `themes/macharte/favicon.png` s'il existe |

### Prévisualiser sans changer la configuration

Si le mode démonstration est activé, une barre apparaît en bas de la boutique
avec un sélecteur de thème. Le thème choisi ne vaut que pour votre propre
navigateur : c'est le moyen le plus simple de comparer plusieurs pistes
graphiques sur une boutique en fonctionnement.

## Étape 5 - Redéfinir des gabarits

Les pages sont écrites en **Blade**, un langage de gabarits très proche du HTML.

### La syntaxe qu'il faut connaître

| Écriture | Rôle |
|----------|------|
| `{{ $variable }}` | Affiche une valeur, en échappant le HTML. **À utiliser par défaut.** |
| `{!! $variable !!}` | Affiche une valeur sans échapper. Réservé aux contenus déjà en HTML (descriptions produit, URL). |
| `@extends('app')` | Indique que la page s'insère dans le squelette `app.blade.php` |
| `@section('content') ... @endsection` | Délimite le contenu injecté dans le squelette |
| `@yield('content')` | Emplacement où le contenu sera injecté (dans `app.blade.php`) |
| `@include('tools.productcard', ['article' => $a])` | Insère un morceau partagé, avec ses paramètres |
| `@if (...) ... @else ... @endif` | Condition |
| `@foreach ($liste as $item) ... @endforeach` | Boucle |
| `{{-- commentaire --}}` | Commentaire qui n'apparaît pas dans le HTML produit |
| `{{ __('MaCle') }}` | Texte traduit |

Une page type ressemble à ceci :

```blade
@extends('app')

@section('content')
	<section class="py-12 bg-base-200">
		<div class="container mx-auto px-4">
			<h1 class="text-3xl font-bold">{{ $title }}</h1>
		</div>
	</section>
@endsection
```

### Règle de copie

Quand vous décidez de redéfinir un gabarit :

1. **Partez toujours de la version du jour de `themes/default/`**, jamais d'une
   copie prise dans un autre thème.
2. Conservez toutes les variables et tous les `@include` d'origine. Le style se
   change, la logique se garde.
3. Ne réécrivez jamais en ligne un morceau qui existe en partagé. Si le partagé
   ne vous convient pas visuellement, créez votre propre
   `themes/macharte/tools/<nom>.blade.php` avec la même interface : le repli
   fera le reste et la page appelante n'a pas à savoir quel thème est actif.

## Anatomie des gabarits

### Structure générale

| Fichier | Rôle |
|---------|------|
| `app.blade.php` | Squelette HTML : `<head>`, métadonnées SEO, appel du CSS, en-tête, pied de page. Toutes les pages en héritent. |
| `header.blade.php` | Barre de navigation |
| `footer.blade.php` | Pied de page |
| `alert.blade.php` | Bandeau d'information affiché en haut de toutes les pages |

### Pages boutique

| Fichier | Page |
|---------|------|
| `index.blade.php` | Accueil et pages catégorie |
| `product.blade.php` | Fiche produit |
| `bundle.blade.php` | Fiche pack |
| `basket.blade.php` | Panier |
| `search.blade.php` | Résultats de recherche |
| `promotions.blade.php` | Promotions en cours |
| `contracts.blade.php`, `contract.blade.php` | Catalogue et fiche des contrats |
| `facturerec.blade.php` | Abonnement |
| `page.blade.php` | Page de contenu libre |
| `faq.blade.php`, `gallery.blade.php` | FAQ et galerie clients |
| `404.blade.php`, `disabled.blade.php` | Erreur et boutique fermée |

### Parcours client

| Fichier | Page |
|---------|------|
| `login.blade.php`, `register.blade.php` | Connexion, création de compte |
| `newpasswd.blade.php`, `newpasswd_set.blade.php` | Mot de passe oublié |
| `formorder.blade.php`, `formorderbasket.blade.php` | Formulaires de commande |
| `orderdone.blade.php`, `quotedone.blade.php`, `registerdone.blade.php` | Confirmations |

### Portail client

`dashboard.blade.php` et le dossier `dashboard/` (une trentaine de fichiers :
`orders`, `invoices`, `contracts`, `wishlist`, `returns_list`, `myaccount`...).
La plupart des thèmes ne les redéfinissent pas : ce sont des pages de gestion
où le CSS suffit largement.

### Morceaux partagés (`tools/`)

Ce sont les briques réutilisées d'une page à l'autre. Les plus utiles à un
webdesigner :

| Partial | Appelé depuis | Rôle |
|---------|---------------|------|
| `tools/productcard` | `index`, `promotions` | **La carte produit du catalogue** : image, badges, prix promotionnel, coeur favori, ajout au panier. Le premier fichier à personnaliser après le CSS. |
| `tools/hero` | `index`, `contracts` | Bannière et carrousel d'accueil |
| `tools/shopmenu` | pages boutique | Menu des catégories |
| `tools/header_categories` | `header` | Catégories dans la barre de navigation |
| `tools/bundle_card` | `index` | Carte de pack, avec l'économie réalisée |
| `tools/trustbadges`, `tools/faq`, `tools/newsletter`, `tools/ugc_homepage` | `index` | Blocs de réassurance et d'animation de la page d'accueil |
| `tools/footer_freeblock` | `footer` | Bloc de contenu libre saisi par le commerçant (`$footer_col1_title`, `$footer_col1_html`) |
| `tools/category_header`, `tools/category_pager`, `tools/category_toolbar` | pages catégorie | Titre, pagination et filtres |
| `tools/wishlist_heart` | cartes et fiche produit | Bouton favori |
| `tools/wishlist_js` | `app` | Script de bascule des favoris |
| `tools/popup` | `dashboard` | Fenêtres modales des listes du portail |
| `tools/variantselector` | `product` | Sélecteur de variantes (taille, couleur) |
| `tools/cart_coupon_form`, `tools/discountcode` | `basket` | Codes promotionnels |

## Le contrat à ne pas casser

Ces points sont vérifiés automatiquement par la suite de tests du module. Un
thème qui les enfreint fait échouer les tests et casse des fonctionnalités que
le commerçant a payées.

### Éléments obligatoires par gabarit

Si vous redéfinissez `header.blade.php`, il doit contenir :

| Élément | Marqueur à conserver |
|---------|----------------------|
| Recherche produit | un lien vers `/search` |
| Accès au panier | un lien vers `/basket` |
| Accès au compte | un lien vers `/login` |
| Sélecteur de langue | `$available_langs` |
| Pages publiées au menu | `$pages_menu_header` |
| Catégories dans l'en-tête | `$header_categories` |
| Nom de société à côté du logo | `$header_show_socname` |

Si vous redéfinissez `footer.blade.php` : `$pages_menu_footer`, `$socname` et le
bloc libre du commerçant, soit par `@include('tools.footer_freeblock')`, soit en
lisant directement `$footer_col1_title` et `$footer_col1_html`.

Si vous redéfinissez `product.blade.php` : `$product_is_promo`,
`$product_promo_percent` et `$product_price_promo`, sans quoi les promotions
disparaissent de la fiche produit.

Si vous redéfinissez `register.blade.php` : `$messageWarn` (le retour d'erreur
du contrôle serveur, sans lequel un formulaire refusé se réaffiche sans aucune
explication) et `$register_vat_required`.

Si vous redéfinissez `index.blade.php` : la grille du catalogue doit passer par
`@include('tools.productcard', ...)`, jamais par une carte écrite en ligne.

### Crochets JavaScript à préserver

Le module fournit des comportements qui s'accrochent à des attributs précis. Si
vous refaites un bloc HTML, gardez-les.

| Crochet | Où | Rôle |
|---------|-----|------|
| `data-submit-once` sur un `<form>` | formulaires de commande | Empêche le double envoi. `data-loading-text` sur le bouton personnalise le libellé d'attente. |
| `class="opb-wishlist-heart"` + `data-pid`, `data-in`, `data-label-add`, `data-label-remove` | bouton favori | Bascule du favori sans rechargement |
| `@include('tools.wishlist_js')` dans `app.blade.php` | squelette | **Sans lui, aucun bouton coeur ne réagit sur tout le site** |
| identifiants `cus_email`, `cus_soc`, `cus_vat`, `cus_address`, `cus_zip`, `cus_town`, `selectcus_country`, `cus_qty`, `cus_unitprice`, `cus_tot` | formulaires de commande | Calcul de la TVA et des totaux en direct |
| attribut `data-theme` sur `<html>` | `app.blade.php` | Bascule clair / sombre |

## Les données disponibles dans les gabarits

Toutes les pages reçoivent ce contexte commun. Utilisez-le plutôt que d'écrire
des valeurs en dur.

### Identité et coordonnées

| Variable | Contenu |
|----------|---------|
| `$socname` | Nom de la société |
| `$shop_phone`, `$shop_email`, `$shop_town`, `$shop_country` | Coordonnées publiques |
| `$footer_line1`, `$footer_line2`, `$footer_line3` | Mentions légales prêtes à afficher : siège, forme juridique et capital, numéros professionnels et TVA |
| `$display_footer_socinfo` | Le commerçant veut-il afficher ces mentions |
| `$opb_version` | Version du module |

### Page et référencement

| Variable | Contenu |
|----------|---------|
| `$title`, `$desc` | Titre et description de la page |
| `$canonical` | Adresse de base de la boutique. **Sert à construire toutes les URL d'images et de ressources.** |
| `$relativepath`, `$relativepathindex` | Chemins relatifs à utiliser pour les liens internes |
| `$og_url`, `$og_type`, `$og_image` | Open Graph, pour le partage sur les réseaux sociaux |
| `$page_hreflangs` | Versions linguistiques de la page |
| `$og_locale` | Langue de la page au format Open Graph (`fr_FR`) |
| `$lang` | Code de langue court de la page (`fr`), pour l'attribut `lang` de la balise `<html>` |
| `$jsonld_payloads`, `$faq_jsonld` | Données structurées Schema.org |
| `$css_url` | **Adresse complète de la feuille de style du thème**, empreinte de cache comprise. À utiliser telle quelle dans `app.blade.php`. |
| `$favicon_url` | Adresse complète du favicon |
| `$csstime` | Empreinte de la feuille de style, si vous construisez l'URL vous-même |

> **Attention.** `$canonical` est l'adresse **de base** de la boutique, pas
> l'adresse de la page courante. Pour la balise canonique, les gabarits écrivent
> `{{ $og_url ?? $canonical }}`. Ne redéfinissez pas `$canonical` : vous
> casseriez toutes les URL d'images.

### Contenus de la page d'accueil

| Variable | Contenu |
|----------|---------|
| `$homeimage`, `$homeimage_mode` | Bannière et mode d'affichage (image seule, carrousel, ou les deux) |
| `$carousel_images`, `$carousel_speed` | Images du carrousel et vitesse de transition |
| `$background_image`, `$background_images` | Images de fond |
| `$alert` | Bandeau d'alerte actif |

### Menus et navigation

| Variable | Contenu |
|----------|---------|
| `$pages_menu_header`, `$pages_menu_footer` | Pages de contenu publiées dans le menu et le pied de page |
| `$header_categories` | Catégories à afficher dans l'en-tête |
| `$portal_menu` | Entrées du menu du portail client |
| `$available_langs` | Langues proposées au visiteur |
| `$link_company`, `$link_contact`, `$link_legal`, `$link_cgv`, `$link_help_center`, `$link_target` | Liens du pied de page configurés en administration |
| `$nbCols` | Nombre de colonnes à prévoir dans le pied de page |

### Fonctionnalités activées

Ces indicateurs valent 1 ou vide. Ils servent à masquer proprement ce que le
commerçant n'a pas activé.

`$setup_shop_enabled`, `$setup_portal_enabled`, `$setup_payment_enabled`,
`$setup_use_ttc` (prix affichés TTC ou HT), `$setup_stock_enabled`,
`$setup_support_enabled`, `$setup_show_quotation`,
`$setup_show_quotation_button`, `$setup_portal_pay_enabled`,
`$setup_portal_sign_enabled`, `$setup_portal_shop_basket_enabled`,
`$withdrawal_enabled`, `$returns_enabled`, `$wishlist_enabled`,
`$prices_visible` (les prix sont-ils réservés aux clients connectés),
`$prices_hidden_text`.

### Client connecté

`$auth_login`, `$cus_soc`, `$cus_name`, `$cus_firstname`, `$cus_email`,
`$cus_phone`, `$cus_address`, `$cus_zip`, `$cus_town`, `$cus_country`,
`$cus_vat`, `$cus_url`, `$wishlist_count`, `$country_select` (liste déroulante
des pays, déjà construite).

### Formulaires

| Variable | Contenu |
|----------|---------|
| `$newToken` | **Jeton de sécurité anti-falsification. Obligatoire dans tout formulaire.** |
| `$form_action` | Adresse de soumission |
| `$messageWarn`, `$errormessage` | Messages à afficher au visiteur |

Tout formulaire doit contenir :

```blade
<input type="hidden" name="token" value="{{ $newToken }}">
```

## Les images

Les images ne sont jamais servies par leur chemin de fichier, mais par un script
qui contrôle les droits d'accès :

```blade
<img src="{!! $canonical !!}/image.php?i={{ $article->imghash }}" alt="{{ $article->label }}">
```

| Ressource | Adresse |
|-----------|---------|
| Image de produit ou de bannière | `{!! $canonical !!}/image.php?i={{ $hash }}` |
| Logo de la boutique | `{!! $canonical !!}/image.php?i=logo` |
| Favicon | `{!! $favicon_url !!}` |
| Feuille de style | `{!! $css_url !!}` |

`$css_url` et `$favicon_url` sont fournies prêtes à l'emploi : elles portent
l'empreinte de cache et, en mode démonstration, le thème choisi par le
visiteur. Ne les reconstruisez pas à la main, sinon la prévisualisation de
thème servira la feuille de style d'un autre thème.

Pensez au chargement différé sur les grilles de produits : les quatre premières
cartes en `loading="eager"` et `fetchpriority="high"`, les suivantes en
`loading="lazy"`. Le partial `tools/productcard` de `default` montre le motif.

## Les textes

**Aucun texte en dur dans un gabarit.** La boutique existe en huit langues
(français, anglais, espagnol, italien, allemand, néerlandais, polonais,
portugais du Brésil). Tout libellé passe par :

```blade
{{ __('opbAddToCart') }}
```

Si votre thème introduit un libellé qui n'existe pas encore, la clé doit être
ajoutée dans les fichiers `langs/*/onepagebasket.lang`, **dans les huit
langues**. Une clé manquante s'affiche telle quelle à l'écran.

Pour les textes réutilisés d'une page à l'autre, cherchez d'abord la clé
existante dans `langs/fr_FR/onepagebasket.lang` avant d'en créer une.

## Une couleur pilotable par le commerçant

Le réglage **Couleur principale du thème** (onglet Apparence) permet au
commerçant de recolorer la boutique sans vous rappeler ni recompiler le CSS.
Pour en profiter, créez un partial `tools/theme_color.blade.php` dans votre
thème, inclus depuis votre `app.blade.php`, qui redéfinit les variables de
couleur à l'exécution.

Trois fonctions utilitaires sont disponibles :

| Fonction | Rôle |
|----------|------|
| `opb_color_normalize($valeur)` | Nettoie et normalise une couleur hexadécimale |
| `opb_color_shade($couleur, $facteur)` | Éclaircit ou assombrit (facteur inférieur à 1 = plus sombre) |
| `opb_color_readable($couleur)` | Renvoie le noir ou le blanc, celui qui reste lisible sur cette couleur |

Le thème `shopiplace` implémente ce motif : c'est le modèle à recopier. Une
palette entière y est dérivée d'une seule couleur saisie par le commerçant.

## Règles de production

- **Jamais de ressource distante.** Ni CDN, ni Google Fonts, ni bibliothèque
  d'icônes en ligne. Tout doit fonctionner sur une boutique sans accès Internet
  sortant, et rien ne doit fuiter vers un tiers pour des raisons de RGPD.
  FontAwesome est déjà embarqué localement et disponible dans tous les thèmes.
- **Jamais de style en ligne** (`style="..."`) sur les éléments. Le CSS est
  compilé : un style en ligne échappe au thème et devient impossible à surcharger.
  L'exception admise est le partial `theme_color` décrit ci-dessus, dont c'est
  précisément le rôle.
- **Utilisez les classes déjà présentes** dans le codebase. Une classe Tailwind
  employée nulle part ailleurs et écrite dans un fichier non scanné ne sera pas
  compilée : elle n'aura aucun effet.
- **Le mobile d'abord.** La boutique est majoritairement consultée sur
  téléphone. Concevez en mobile puis élargissez avec `sm:`, `md:`, `lg:`.
- **Le mode sombre est attendu.** Chaque teinte a sa contrepartie sombre via le
  bloc `[data-theme="dark"]` et les variantes `dark:`.
- **L'accessibilité n'est pas optionnelle.** Attribut `alt` sur les images,
  `aria-label` sur les boutons sans texte, contraste suffisant entre les couleurs
  de fond et de texte, ordre de tabulation cohérent.

## Checklist avant livraison

- [ ] `input.css` contient le bloc `@source` complet, `source(none)` inclus
- [ ] `index.css` est recompilé et livré avec les gabarits
- [ ] Les polices sont dans `public/fonts/` et référencées en `./fonts/...`
- [ ] Aucune adresse distante dans le CSS ni dans les gabarits
- [ ] Aucun texte en dur : tout passe par `__('...')`
- [ ] Les clés de traduction nouvelles existent dans les huit langues
- [ ] Chaque formulaire contient `{{ $newToken }}`
- [ ] Les marqueurs obligatoires sont présents dans les gabarits redéfinis
- [ ] Les crochets JavaScript sont conservés
- [ ] Rendu vérifié en mode clair et en mode sombre
- [ ] Rendu vérifié sur téléphone, tablette et grand écran
- [ ] Les pages non redéfinies (portail client, confirmations) restent lisibles
- [ ] La cible `css-macharte` est déclarée dans le `Makefile`
- [ ] `favicon.png` fourni si vous voulez un favicon propre au thème

Si vous avez accès à l'environnement de développement, lancez la suite de tests :
elle signale précisément les marqueurs manquants.

```bash
vendor/bin/phpunit -c phpunit.xml
```

## Erreurs fréquentes

| Symptôme | Cause | Correction |
|----------|-------|------------|
| La boutique s'affiche sans aucun style sur les pages non redéfinies | Les lignes `@source "../default/..."` manquent dans `input.css` | Reprendre le bloc `@source` complet |
| Une classe ajoutée n'a aucun effet | `index.css` n'a pas été recompilé | `make css-macharte` |
| Le CSS pèse plusieurs mégaoctets | `source(none)` a été retiré | Le remettre |
| Les boutons favoris ne réagissent plus | `@include('tools.wishlist_js')` a disparu de `app.blade.php` | Le rétablir |
| Les promotions n'apparaissent plus sur la fiche produit | `product.blade.php` a été copié sans les variables de promotion | Repartir de la version `default` du jour |
| Toutes les images sont cassées | `$canonical` a été redéfini dans un gabarit | Ne jamais le redéfinir, utiliser `$og_url` pour l'adresse de la page |
| Un libellé s'affiche sous forme de code | La clé de traduction n'existe pas | L'ajouter dans `langs/*/onepagebasket.lang` |
| Le thème n'apparaît pas dans la liste | Le nom du dossier contient un caractère non autorisé | Lettres, chiffres, tirets et soulignés uniquement |
| Le formulaire est refusé sans message | Le jeton `{{ $newToken }}` manque | L'ajouter en champ caché |

## Par où commencer concrètement

1. Copiez `themes/lumen/input.css` dans `themes/macharte/input.css`.
2. Remplacez les valeurs de couleur par celles de votre charte, dans les deux
   blocs clair et sombre.
3. Ajoutez la cible `css-macharte` au `Makefile.dist` et lancez
   `make css-macharte`.
4. Activez le thème dans l'onglet Apparence, et regardez : vous avez déjà une
   boutique complète à vos couleurs.
5. Copiez ensuite `themes/default/tools/productcard.blade.php` dans votre thème
   et retravaillez la carte produit. C'est l'élément le plus vu de la boutique.
6. Puis `header.blade.php` et `footer.blade.php`, en gardant les marqueurs
   obligatoires.
7. Enfin `index.blade.php` si la page d'accueil demande une composition
   particulière.

Arrêtez-vous dès que le résultat vous convient : chaque fichier que vous n'avez
pas copié est un fichier qui continuera de s'améliorer tout seul au fil des
mises à jour du module.

## Voir aussi

- [Configuration générale](configuration.md) - où activer le thème et régler
  logo, favicon, carrousel et langues
- [La boutique en ligne](boutique.md) - les fonctionnalités que votre thème doit
  savoir afficher
- [Pages de contenu](pages.md) - les pages libres alimentées par le commerçant
