Ordelyx
Retour à l'accueil

Structure du thème Ordelyx

Référence technique complète de « Modifier le code » — le thème de base d'une boutique Ordelyx s'appelle « Franc ». Cette page est générée directement depuis le code source de la plateforme : chaque bloc JSON ci-dessous est la valeur exacte, caractère pour caractère, qu'utilise la plateforme — jamais une reconstitution manuelle susceptible de diverger. Elle s'adresse à toute personne, ou tout assistant IA (Claude, ChatGPT, Gemini…), qui veut comprendre ou reproduire cette architecture pour construire un thème entièrement personnalisé.

Dernière mise à jour : 2026-09-07 — comportement des paramètres vides, échappement/XSS, limite repeater+image, accessibilité, composition entre sections empilées, exemple include à deux niveaux.

1. Vue d'ensemble

L'éditeur « Modifier le code » (Boutique > Thèmes > ouvrir un thème > « Modifier le code ») expose la structure du thème d'une boutique comme une arborescence de fichiers réelle. Deux catégories de nœuds cohabitent, et il est essentiel de savoir laquelle est laquelle avant de modifier quoi que ce soit :

  • Réglages gérés par Ordelyx (layout/, templates/, sections/<page>/<section-fixe>.json, config/theme.json) — des vues JSON sur des réglages structurés d'un composant déjà construit par Ordelyx (titres, couleurs, activé/désactivé…). On peut y ajouter des clés en plus de celles d'origine sans rien casser — une clé en trop est conservée mais ignorée tant qu'aucun composant ne la lit, une clé manquante retombe sur sa valeur par défaut. Seul un JSON syntaxiquement invalide (ou qui n'est pas un objet) est refusé à l'enregistrement.
  • Vos fichiers (assets/, snippets/, et le contenu de chaque sections/<page>/custom/<id>/) — de vrais fichiers créés, renommés, organisés en sous-dossiers et supprimés librement, contenant tout le HTML/CSS/JS écrit volontairement. C'est le seul mécanisme pour construire quelque chose qui n'existe pas déjà dans la liste fixe d'Ordelyx — aussi élaboré que ce soit, plusieurs fichiers, plusieurs dossiers, sans limite de complexité (voir §7).

Une section fixe ne peut jamais être supprimée de la structure, seulement désactivée ("enabled": false) — ces composants sont partagés par toutes les boutiques Ordelyx, ce n'est pas une limite technique oubliée. Pour aller au-delà de ce que ses réglages permettent, le bouton « Convertir en section personnalisée » (dans l'éditeur visuel) la remplace par une version 100 % personnalisable, pré-remplie avec l'équivalent de son rendu actuel, à la même position dans la page — un point de départ éditable à volonté plutôt que de forcer un composant fixe à faire autre chose. Chaque « Enregistrer » crée automatiquement une version dans l'historique de l'éditeur — en cas d'erreur, on restaure plutôt que de tenter de reconstruire de mémoire.

2. Arborescence complète

layout/
  annonce.json          # bandeau du haut (AnnouncementSettings)
  en-tete.json          # en-tête / navigation (HeaderSettings)
  whatsapp.json         # bouton flottant WhatsApp (WhatsappSettings)
  contact.json          # formulaire de contact en bas de page (ContactSectionSettings)
  pied-de-page.json     # pied de page (FooterSettings)
templates/
  index.json             # ordre des sections de l'accueil
  produit.json           # ordre des sections de la fiche produit
sections/
  accueil/
    <section-fixe>.json  # une par section fixe (15 — voir §4)
    custom/
      <id>/               # une section personnalisée (voir §7)
  fiche-produit/
    <section-fixe>.json  # une par section fixe (15 — voir §5)
    custom/
      <id>/
  catalogue/
    custom/               # pas de fichier fixe — un seul point d'ancrage "productGrid" (voir §6)
      <id>/
  page-<id-de-la-page>/    # une par page personnalisée VISIBLE
    custom/                # un seul point d'ancrage "content" (voir §6)
      <id>/
assets/
  site.html / site.css / site.js   # code sitewide (voir §8)
  <vos fichiers>.css / .js / .html # auto-injectés sur toute la boutique, ordre alphabétique
snippets/
  <vos fichiers>          # jamais injectés automatiquement, réutilisés via {{ include:'...' }}
config/
  theme.json              # couleur d'accent, logo, couleur de fond, favicon

layout/ couvre les éléments présents sur toutes les pages. Un fichier templates/*.json ne contient qu'un tableau sections qui liste, dans l'ordre d'affichage, les sections de cette page (par leur clé fixe, ex. "banner", ou custom:<id> pour une section personnalisée) — réordonner ce tableau réordonne la page, c'est l'équivalent JSON du glisser-déposer de l'éditeur visuel. Le Catalogue et les pages personnalisées n'ont pas de fichier templates/*.json séparé : n'ayant qu'un seul élément fixe chacun, leur ordre se gère directement dans leur propre sections/<page>/custom/ (§6).

3. Réglages globaux — layout/ et config/

Contenu exact d'une boutique fraîchement créée (avant toute personnalisation) :

layout/annonce.json
{
  "enabled": false,
  "mode": "fade",
  "speed": "normal",
  "dismissible": true,
  "backgroundColor": "#17181B",
  "textColor": "#FFFFFF",
  "messages": [
    {
      "id": "msg-1",
      "text": "Livraison rapide dans toute la ville",
      "href": ""
    },
    {
      "id": "msg-2",
      "text": "Paiement à la réception de votre commande",
      "href": ""
    },
    {
      "id": "msg-3",
      "text": "Nouveaux produits chaque semaine",
      "href": ""
    }
  ]
}
layout/en-tete.json
{
  "enabled": true,
  "logoPosition": "left",
  "logoSize": "medium",
  "navPosition": "center",
  "height": "standard",
  "sticky": true,
  "bottomBorder": true,
  "navStyle": "inline",
  "ctaEnabled": false,
  "ctaLabel": "Commander maintenant",
  "ctaHref": "/produits",
  "backgroundColor": null,
  "textColor": null
}
layout/whatsapp.json
{
  "enabled": false,
  "phone": "",
  "message": "Bonjour, j'ai une question sur un produit",
  "position": "right",
  "showBubble": true
}
layout/contact.json
{
  "enabled": false,
  "title": "Une question ?",
  "description": "Écrivez-nous, on vous répond directement à votre adresse email.",
  "backgroundColor": null,
  "cardBackgroundColor": null,
  "iconColor": null,
  "buttonColor": null,
  "buttonTextColor": null
}
layout/pied-de-page.json
{
  "enabled": true,
  "aboutText": "",
  "contactPhone": "",
  "contactEmail": "",
  "showPaymentBadges": true,
  "social": {
    "facebook": "",
    "instagram": "",
    "tiktok": "",
    "whatsapp": ""
  },
  "backgroundColor": "#17181B",
  "textColor": "#FFFFFF",
  "layout": "columns",
  "borderColor": null,
  "copyrightText": "",
  "showLogo": true,
  "padding": "standard",
  "showPoweredBy": true
}
config/theme.json
{
  "logoUrl": null,
  "faviconUrl": null,
  "accentColor": "#4F46E5",
  "backgroundColor": null
}

config/theme.json reflète des réglages qui vivent ailleurs en base (couleur/logo sur la boutique, favicon sur le thème) — l'éditeur les rassemble ici en lecture/écriture pour ne pas obliger à changer d'écran.

4. Page d'accueil — 15 sections par défaut

templates/index.json d'une boutique fraîchement créée — l'ordre initial, du haut vers le bas de la page :

templates/index.json
{
  "sections": [
    "banner",
    "trustBar",
    "collections",
    "imageText",
    "mutedVideo",
    "imageCarousel",
    "beforeAfter",
    "howItWorks",
    "countdown",
    "reviews",
    "ugcGallery",
    "partnerLogos",
    "faq",
    "guarantee",
    "mediaMarquee"
  ]
}

Chacune des 15 sections fixes de l'accueil, avec son contenu par défaut exact :

sections/accueil/banner.jsonBannière
{
  "enabled": false,
  "imageUrl": null,
  "overlayOpacity": 30,
  "textPosition": "center",
  "title": "Découvrez notre nouvelle collection",
  "subtitle": "Livraison rapide et paiement à la réception",
  "buttonLabel": "Voir la boutique",
  "buttonHref": "/produits",
  "backgroundColor": null,
  "height": "medium",
  "textColor": null,
  "buttonColor": null,
  "buttonTextColor": null,
  "mobileImageUrl": null
}
sections/accueil/trust-bar.jsonRéassurance
{
  "enabled": false,
  "items": [
    {
      "id": "trust-1",
      "icon": "truck",
      "text": "Livraison rapide"
    },
    {
      "id": "trust-2",
      "icon": "hand-coins",
      "text": "Paiement à la réception"
    },
    {
      "id": "trust-3",
      "icon": "phone",
      "text": "Assistance par téléphone"
    },
    {
      "id": "trust-4",
      "icon": "rotate-ccw",
      "text": "Retour possible sous 48h"
    }
  ],
  "backgroundColor": null,
  "iconColor": null
}
sections/accueil/collections.jsonCollections
{
  "enabled": false,
  "title": "Nos collections",
  "selectedCollectionIds": [],
  "displayStyle": "grid",
  "backgroundColor": null
}
sections/accueil/image-text.jsonImage + Texte
{
  "enabled": false,
  "imageUrl": null,
  "layout": "imageLeft",
  "eyebrow": "",
  "title": "Faites-en l'expérience",
  "body": "Décrivez ici ce qui rend votre produit ou votre boutique unique.",
  "buttonLabel": "En savoir plus",
  "buttonHref": "/produits",
  "backgroundColor": "",
  "textColor": "",
  "imageRadius": "large",
  "imageAspect": "video",
  "buttonColor": null,
  "buttonTextColor": null,
  "eyebrowColor": null
}
sections/accueil/muted-video.jsonVidéo muette
{
  "enabled": false,
  "videoUrl": null,
  "posterUrl": null,
  "title": "",
  "subtitle": "",
  "overlayOpacity": 20,
  "minHeight": "medium"
}
sections/accueil/image-carousel.jsonCarrousel d'images (accueil)
{
  "enabled": false,
  "title": "En images",
  "showTitle": true,
  "images": [],
  "backgroundColor": null,
  "showCounter": false,
  "counterPosition": "bottom-right",
  "slidesPerView": {
    "desktop": 1,
    "tablet": 1,
    "mobile": 1
  },
  "slideGap": 16,
  "heightMode": "auto",
  "heightPx": 420,
  "edgeAlign": "center",
  "imageRatio": "landscape",
  "imageRadius": "medium",
  "imageShadow": false,
  "hoverZoom": false,
  "imageFilter": "none",
  "arrowsEnabled": true,
  "arrowStyle": "circle-filled",
  "arrowSize": 36,
  "arrowPosition": "inside",
  "arrowsVisibleOnMobile": false,
  "arrowBgColor": null,
  "arrowIconColor": null,
  "arrowBgHoverColor": null,
  "arrowIconHoverColor": null,
  "dotsEnabled": true,
  "dotShape": "round",
  "dotPosition": "overlay",
  "dotInactiveColor": null,
  "dotActiveColor": null,
  "transitionType": "slide",
  "transitionSpeed": "normal",
  "textAnimation": "fade",
  "autoplayEnabled": false,
  "autoplayInterval": 5,
  "loop": true,
  "lightboxEnabled": true
}
sections/accueil/before-after.jsonAvant / Après (accueil)
{
  "enabled": false,
  "title": "Avant / Après",
  "description": "",
  "beforeLabel": "Avant",
  "afterLabel": "Après",
  "beforeImageUrl": null,
  "afterImageUrl": null,
  "backgroundColor": null
}
sections/accueil/how-it-works.jsonComment ça marche
{
  "enabled": false,
  "layout": "grid",
  "accentColor": null,
  "titleColor": null,
  "title": "Comment ça marche",
  "steps": [
    {
      "id": "step-1",
      "title": "Choisissez votre produit",
      "description": "Parcourez le catalogue et sélectionnez ce qui vous plaît."
    },
    {
      "id": "step-2",
      "title": "Commandez en 1 minute",
      "description": "Renseignez juste votre nom, votre numéro et votre adresse."
    },
    {
      "id": "step-3",
      "title": "Recevez chez vous",
      "description": "On vous livre à l'adresse indiquée."
    },
    {
      "id": "step-4",
      "title": "Payez à la réception",
      "description": "Vous payez seulement quand vous avez le produit en main."
    }
  ],
  "backgroundColor": null
}
sections/accueil/countdown.jsonCompte à rebours
{
  "enabled": false,
  "title": "Offre à durée limitée",
  "endDate": "",
  "durationHours": 24,
  "expiredText": "Offre terminée",
  "backgroundColor": "#DC2626",
  "textColor": "#FFFFFF",
  "size": "medium",
  "showDays": true,
  "digitBackgroundColor": null,
  "digitTextColor": null,
  "radius": 6
}
sections/accueil/reviews.jsonAvis clients (accueil)
{
  "enabled": false,
  "title": "Avis clients",
  "showAverageRating": true,
  "titleColor": "#111827",
  "textColor": "#374151",
  "starColor": "#F59E0B",
  "backgroundColor": null,
  "reviews": [],
  "cardBackgroundColor": null,
  "cardBorderColor": null,
  "layout": "carousel"
}
sections/accueil/ugc-gallery.jsonGalerie clients (accueil)
{
  "enabled": false,
  "title": "Nos clients en parlent",
  "images": [],
  "backgroundColor": null,
  "columns": 4,
  "radius": "medium",
  "titleColor": null,
  "showCaption": true
}
sections/accueil/partner-logos.jsonLogos partenaires (accueil)
{
  "enabled": false,
  "title": "Ils nous font confiance",
  "logos": [],
  "backgroundColor": null,
  "grayscale": true,
  "logoSize": "medium",
  "layout": "row",
  "gap": 40,
  "titleColor": null
}
sections/accueil/faq.jsonFAQ (accueil)
{
  "enabled": false,
  "title": "Questions fréquentes",
  "items": [],
  "backgroundColor": null,
  "sectionPaddingY": 40,
  "itemGap": 10,
  "itemPaddingY": 14,
  "maxWidth": 672,
  "questionFontSize": 14,
  "answerFontSize": 14,
  "radius": "medium",
  "iconStyle": "plus",
  "iconRotate": true,
  "iconColor": null,
  "borderColor": null,
  "borderColorOpen": null,
  "openBackgroundColor": null,
  "borderWidth": 1,
  "questionColor": null,
  "questionColorOpen": null,
  "answerColor": null,
  "answerAnimation": "none",
  "animationSpeed": "normal",
  "exclusiveAccordion": false
}
sections/accueil/guarantee.jsonGarantie (accueil)
{
  "enabled": false,
  "icon": "shield-check",
  "title": "Satisfait ou remboursé",
  "description": "Paiement à la livraison, retour possible sous 48h si le produit ne convient pas.",
  "backgroundColor": null,
  "iconColor": null
}
sections/accueil/media-marquee.jsonBande défilante
{
  "enabled": false,
  "items": [],
  "speed": "normal",
  "direction": "left",
  "itemHeight": "medium",
  "backgroundColor": null,
  "pauseOnHover": true,
  "grayscale": false
}

Aucune de ces 15 sections n'est activée sur une boutique neuve — chacune a "enabled": false tant que le marchand ne l'active pas explicitement (voir la valeur enabled de chaque bloc ci-dessus, c'est la source de vérité). Seuls l'en-tête et le pied de page (layout/, §3) démarrent actifs.

5. Fiche produit — 16 sections par défaut

templates/produit.json
{
  "sections": [
    "trustBar",
    "imageCarousel",
    "video",
    "beforeAfter",
    "specs",
    "howItWorks",
    "banner",
    "imageText",
    "countdown",
    "reviews",
    "ugcGallery",
    "partnerLogos",
    "faq",
    "guarantee",
    "relatedProducts"
  ]
}
sections/fiche-produit/informations-produit.jsonInformations produit
{
  "order": [
    "title",
    "price",
    "stock",
    "bullets",
    "quantity",
    "orderForm",
    "stockBar",
    "recentSales",
    "liveOrders",
    "description"
  ],
  "showTitle": true,
  "showPrice": true,
  "showStock": true,
  "stockText": "",
  "stockColor": "",
  "showDescription": true,
  "showQuantity": true,
  "showAddToCart": true,
  "showBullets": false,
  "bullets": [],
  "showStockBar": false,
  "stockBar": {
    "mode": "manual",
    "sold": 0,
    "total": 100,
    "simulateGrowth": false,
    "simulateIntervalMinSeconds": 20,
    "simulateIntervalMaxSeconds": 60,
    "simulateIncrementMax": 2,
    "labelTemplate": "{total} accès au total · plus que {restant} accès disponibles",
    "gradientStart": "#FACC15",
    "gradientEnd": "#DC2626",
    "trackColor": "#E5E7EB",
    "heightPx": 10,
    "roundedCorners": true,
    "radiusPx": 20,
    "alertThresholdPercent": 10,
    "alertColor": "#DC2626",
    "animateOnLoad": true,
    "animationSpeedMs": 900,
    "icon": "hourglass",
    "iconColor": "#DC2626",
    "fontSize": 13,
    "emptyStateEnabled": true,
    "emptyStateMessage": "Offre épuisée"
  },
  "showRecentSales": false,
  "recentSales": {
    "entries": [],
    "fontSize": 14,
    "textColor": "#111827",
    "backgroundColor": "#F9FAFB",
    "badgeIcon": "⭐",
    "iconType": "emoji",
    "iconKey": "shopping-bag",
    "badgeBackgroundColor": null,
    "badgeSize": 32,
    "radius": "large",
    "borderEnabled": true,
    "borderColor": null,
    "shadowEnabled": true,
    "showRating": true,
    "ratingColor": null,
    "speedSeconds": 22
  },
  "showLiveOrders": false,
  "liveOrders": {
    "mode": "manual",
    "entries": [],
    "fontSize": 14,
    "textColor": "#111827",
    "backgroundColor": "#F9FAFB",
    "badgeIcon": "🚚",
    "iconType": "emoji",
    "iconKey": "shopping-bag",
    "badgeBackgroundColor": null,
    "badgeSize": 32,
    "radius": "large",
    "borderEnabled": true,
    "borderColor": null,
    "shadowEnabled": true,
    "animation": "slide-up",
    "animationSpeedMs": 420,
    "intervalSeconds": 5,
    "randomInterval": false,
    "intervalMinSeconds": null,
    "intervalMaxSeconds": null,
    "avatarMode": "initials",
    "closeable": true,
    "maxPerSession": null,
    "initialDelaySeconds": 3,
    "timestampRandomMinMinutes": 1,
    "timestampRandomMaxMinutes": 15
  }
}
sections/fiche-produit/trust-bar.jsonRéassurance produit
{
  "enabled": false,
  "items": [
    {
      "id": "trust-1",
      "icon": "truck",
      "text": "Livraison rapide"
    },
    {
      "id": "trust-2",
      "icon": "hand-coins",
      "text": "Paiement à la réception"
    },
    {
      "id": "trust-3",
      "icon": "phone",
      "text": "Assistance par téléphone"
    },
    {
      "id": "trust-4",
      "icon": "rotate-ccw",
      "text": "Retour possible sous 48h"
    }
  ],
  "backgroundColor": null,
  "iconColor": null
}
sections/fiche-produit/image-carousel.jsonCarrousel d'images
{
  "enabled": false,
  "title": "En images",
  "showTitle": true,
  "images": [],
  "backgroundColor": null,
  "showCounter": false,
  "counterPosition": "bottom-right",
  "slidesPerView": {
    "desktop": 1,
    "tablet": 1,
    "mobile": 1
  },
  "slideGap": 16,
  "heightMode": "auto",
  "heightPx": 420,
  "edgeAlign": "center",
  "imageRatio": "landscape",
  "imageRadius": "medium",
  "imageShadow": false,
  "hoverZoom": false,
  "imageFilter": "none",
  "arrowsEnabled": true,
  "arrowStyle": "circle-filled",
  "arrowSize": 36,
  "arrowPosition": "inside",
  "arrowsVisibleOnMobile": false,
  "arrowBgColor": null,
  "arrowIconColor": null,
  "arrowBgHoverColor": null,
  "arrowIconHoverColor": null,
  "dotsEnabled": true,
  "dotShape": "round",
  "dotPosition": "overlay",
  "dotInactiveColor": null,
  "dotActiveColor": null,
  "transitionType": "slide",
  "transitionSpeed": "normal",
  "textAnimation": "fade",
  "autoplayEnabled": false,
  "autoplayInterval": 5,
  "loop": true,
  "lightboxEnabled": true
}
sections/fiche-produit/video.jsonVidéo produit
{
  "enabled": false,
  "title": "Voir le produit en vidéo",
  "videoUrl": "",
  "posterUrl": null,
  "backgroundColor": null,
  "aspectRatio": "16:9",
  "overlayText": "",
  "overlayTextSize": 24,
  "overlayTextColor": "#FFFFFF",
  "overlayPosition": "bottom-center",
  "overlayBackgroundColor": null
}
sections/fiche-produit/before-after.jsonAvant / Après
{
  "enabled": false,
  "title": "Avant / Après",
  "description": "",
  "beforeLabel": "Avant",
  "afterLabel": "Après",
  "beforeImageUrl": null,
  "afterImageUrl": null,
  "backgroundColor": null
}
sections/fiche-produit/specs.jsonCaractéristiques
{
  "enabled": false,
  "title": "Caractéristiques",
  "specs": [],
  "backgroundColor": null
}
sections/fiche-produit/how-it-works.jsonComment ça marche (produit)
{
  "enabled": false,
  "layout": "grid",
  "accentColor": null,
  "titleColor": null,
  "title": "Comment ça marche",
  "steps": [
    {
      "id": "step-1",
      "title": "Choisissez votre produit",
      "description": "Parcourez le catalogue et sélectionnez ce qui vous plaît."
    },
    {
      "id": "step-2",
      "title": "Commandez en 1 minute",
      "description": "Renseignez juste votre nom, votre numéro et votre adresse."
    },
    {
      "id": "step-3",
      "title": "Recevez chez vous",
      "description": "On vous livre à l'adresse indiquée."
    },
    {
      "id": "step-4",
      "title": "Payez à la réception",
      "description": "Vous payez seulement quand vous avez le produit en main."
    }
  ],
  "backgroundColor": null
}
sections/fiche-produit/banner.jsonBannière produit
{
  "enabled": false,
  "imageUrl": null,
  "overlayOpacity": 30,
  "textPosition": "center",
  "title": "Découvrez notre nouvelle collection",
  "subtitle": "Livraison rapide et paiement à la réception",
  "buttonLabel": "Voir la boutique",
  "buttonHref": "/produits",
  "backgroundColor": null,
  "height": "medium",
  "textColor": null,
  "buttonColor": null,
  "buttonTextColor": null,
  "mobileImageUrl": null
}
sections/fiche-produit/image-text.jsonImage + Texte produit
{
  "enabled": false,
  "imageUrl": null,
  "layout": "imageLeft",
  "eyebrow": "",
  "title": "Faites-en l'expérience",
  "body": "Décrivez ici ce qui rend votre produit ou votre boutique unique.",
  "buttonLabel": "En savoir plus",
  "buttonHref": "/produits",
  "backgroundColor": "",
  "textColor": "",
  "imageRadius": "large",
  "imageAspect": "video",
  "buttonColor": null,
  "buttonTextColor": null,
  "eyebrowColor": null
}
sections/fiche-produit/countdown.jsonCompte à rebours produit
{
  "enabled": false,
  "title": "Offre à durée limitée",
  "endDate": "",
  "durationHours": 24,
  "expiredText": "Offre terminée",
  "backgroundColor": "#DC2626",
  "textColor": "#FFFFFF",
  "size": "medium",
  "showDays": true,
  "digitBackgroundColor": null,
  "digitTextColor": null,
  "radius": 6
}
sections/fiche-produit/reviews.jsonAvis clients
{
  "enabled": false,
  "title": "Avis clients",
  "showAverageRating": true,
  "titleColor": "#111827",
  "textColor": "#374151",
  "starColor": "#F59E0B",
  "backgroundColor": null,
  "reviews": [],
  "cardBackgroundColor": null,
  "cardBorderColor": null,
  "layout": "carousel"
}
sections/fiche-produit/ugc-gallery.jsonGalerie clients
{
  "enabled": false,
  "title": "Nos clients en parlent",
  "images": [],
  "backgroundColor": null,
  "columns": 4,
  "radius": "medium",
  "titleColor": null,
  "showCaption": true
}
sections/fiche-produit/partner-logos.jsonLogos partenaires
{
  "enabled": false,
  "title": "Ils nous font confiance",
  "logos": [],
  "backgroundColor": null,
  "grayscale": true,
  "logoSize": "medium",
  "layout": "row",
  "gap": 40,
  "titleColor": null
}
sections/fiche-produit/faq.jsonFAQ produit
{
  "enabled": false,
  "title": "Questions fréquentes",
  "items": [],
  "backgroundColor": null,
  "sectionPaddingY": 40,
  "itemGap": 10,
  "itemPaddingY": 14,
  "maxWidth": 672,
  "questionFontSize": 14,
  "answerFontSize": 14,
  "radius": "medium",
  "iconStyle": "plus",
  "iconRotate": true,
  "iconColor": null,
  "borderColor": null,
  "borderColorOpen": null,
  "openBackgroundColor": null,
  "borderWidth": 1,
  "questionColor": null,
  "questionColorOpen": null,
  "answerColor": null,
  "answerAnimation": "none",
  "animationSpeed": "normal",
  "exclusiveAccordion": false
}
sections/fiche-produit/guarantee.jsonGarantie
{
  "enabled": false,
  "icon": "shield-check",
  "title": "Satisfait ou remboursé",
  "description": "Paiement à la livraison, retour possible sous 48h si le produit ne convient pas.",
  "backgroundColor": null,
  "iconColor": null
}
sections/fiche-produit/related-products.jsonProduits similaires
{
  "enabled": false,
  "title": "Produits similaires",
  "count": 4,
  "columns": 4,
  "backgroundColor": null,
  "titleColor": null
}

Le bloc « Informations produit » (titre, prix, stock, points clés, quantité, formulaire de commande, notification de vente récente, commandes en direct, description) est éditable en JSON comme les 15 autres depuis informations-produit.json ci-dessus, en plus de l'éditeur visuel (onglet Produit) — les deux modifient exactement le même réglage. Chaque élément de ce bloc, « Informations produit » incluse, se repositionne indépendamment (avant ou juste après le formulaire de commande, par exemple) — voir la note ci-dessous sur les deux nouvelles notifications.

Notification de vente récente et Commandes en direct sont deux bandeaux de preuve sociale, repositionnables librement dans l'ordre du bloc « Informations produit », avant ou après le formulaire de commande. Entièrement personnalisables (2026-09-07) — rien n'est figé, tout le contenu et le style par défaut peuvent être remplacés par les réglages du marchand :

  • Notification de vente récente défile en continu (nom + description + étoiles) — le contenu est entièrement écrit par le marchand.
  • Commandes en direct affiche une seule notification à la fois, animée, avec un délai réglable (en secondes, aléatoire ou non) — sans étoiles. Deux sources : « Manuel » (texte écrit par le marchand) ou « Commandes réelles, en direct » (les vraies commandes récentes de ce produit — prénom du client et quantité uniquement, jamais son téléphone, son adresse ni sa note ; n'affiche rien tant qu'aucune commande récente n'existe, jamais de contenu fabriqué).
  • Style, les deux : taille et couleur du texte, couleur de fond de la carte, type d'icône du badge — un emoji parmi 7 préréglages (comportement historique, inchangé) OU une vraie icône SVG parmi 12 (iconType: "icon" + iconKey, ex. "shopping-bag"), couleur de fond du badge, arrondi (petit/moyen/grand/pilule), bordure (activable, couleur libre), ombre portée (activable). iconType par défaut reste "emoji" pour toute boutique existante — rien ne change tant que le marchand ne bascule pas explicitement sur une icône.
  • « Notification de vente récente » uniquement : étoiles affichables/masquables (showRating), leur couleur personnalisable (ratingColor), et vitesse du défilement en secondes (speedSeconds, 22 par défaut — plus petit = plus rapide).

Isolation par produit : un produit spécifique peut avoir sa propre version d'une de ces sections (activée/désactivée, ou certaines valeurs différentes) sans affecter les autres produits ni le gabarit de la boutique — depuis l'éditeur visuel, avec ce produit sélectionné, un contrôle « Activer sur ce produit uniquement » apparaît sur chaque section. En son absence, le produit suit le gabarit commun de la boutique.

6. Catalogue et pages personnalisées

Le Catalogue et chaque page personnalisée visible n'ont aucune section fixe configurable — juste un seul « point d'ancrage » atomique que les sections personnalisées entourent (avant, après, entre plusieurs) : la grille de produits pour le Catalogue, le titre + texte de la page pour une page personnalisée. Ni l'un ni l'autre n'a de fichier JSON dans l'arborescence — leur seul état, l'ordre et les sections personnalisées, vit dans sections/catalogue/custom/ et sections/page-<id>/custom/ (§7).

sections/catalogue — état par défaut (anchor "productGrid")
{
  "order": [
    "productGrid"
  ],
  "customBlocks": []
}
sections/page-<id> — état par défaut (anchor "content")
{
  "order": [
    "content"
  ],
  "customBlocks": []
}

7. Sections personnalisées — le mécanisme complet

C'est le seul moyen d'ajouter un contenu qui n'existe pas déjà dans la liste fixe — sur n'importe quelle page (accueil, fiche produit, catalogue, ou une page personnalisée), à n'importe quel endroit de l'ordre, avec autant de fichiers et de sous-dossiers que nécessaire. Aucune intervention d'Ordelyx n'est requise : tout se construit depuis l'éditeur.

Une section FIXE elle-même ne prend jamais de params — pour lui ajouter quelque chose (ex. compléter la Bannière avec un contenu que ses réglages d'origine ne couvrent pas), ouvrez son onglet dans l'éditeur visuel et utilisez « Ajouter un complément à cette section » : ça crée une vraie section personnalisée, positionnée automatiquement juste après elle, avec exactement ce même mécanisme — ses réglages d'origine restent inchangés, le complément s'ajoute par-dessus.

Créer une section

Sur sections/<page>/custom/, le « + » propose « Nouvelle section », avec un choix explicite d'emplacement (« Insérer après : … »). Ça crée : un identifiant unique, une entrée dans l'ordre de la page (custom:<id>), le dossier sections/<page>/custom/<id>/, et un premier fichier section.html vide — section.css et section.js restent optionnels ici, à ajouter vous-même via le « + » du dossier si besoin (auto-inclus dès qu'ils existent). « Coller une section » (plus bas) crée en revanche TOUJOURS les trois fichiers, y compris section.js vide si le JSON collé n'en fournissait pas — jamais besoin de le créer à la main après coup pour, par exemple, ajouter une animation au défilement. Tout autre fichier du dossier (données, icônes SVG, sous-composants) n'est utilisé que via {{ include:'...' }} ci-dessous. Un fichier meta.json virtuel gère l'identité de la section :

sections/<page>/custom/<id>/meta.json
{
  "name": "Ma section",
  "enabled": true,
  "params": []
}

Pour chaque paramètre déclaré dans params : default est la valeur de secours (utilisée tant que personne n'a rien changé, jamais modifiée par l'éditeur visuel) et value la valeur ACTUELLEMENT enregistrée (ce que change l'éditeur visuel quand le marchand ajuste un réglage). Les deux démarrent identiques à la création du paramètre, puis évoluent indépendamment — modifier l'une ne touche jamais l'autre.

section.js — cycle de vie et isolation

S'exécute comme un <script> classique, au moment exact où le navigateur atteint cette position dans la page — donc APRÈS le HTML de cette même section, qui existe déjà à cet instant (document.querySelector('[data-ordelyx-section="ID"]') fonctionne), mais AVANT le contenu des sections plus bas dans la page, qui n'existe pas encore (utilisez DOMContentLoaded pour y accéder). Il tourne enveloppé dans sa propre fonction (IIFE) : une var/function déclarée en haut de votre code ne peut jamais entrer en collision avec celle d'une autre section personnalisée sur la même page — pour exposer volontairement quelque chose globalement, faites-le explicitement via window.monTruc = .... Ceci n'isole PAS le DOM ni les écouteurs d'événements globaux (window/document) entre sections, seulement les déclarations de variables/fonctions.

Exemple d'animation déclenchée au défilement — utile pour une section plus bas dans une longue fiche produit, où une animation au chargement de la page serait déjà terminée avant que le visiteur ne fasse défiler jusque-là :

var el = document.querySelector('[data-ordelyx-section="ID_DE_LA_SECTION"]');
if (el) {
  new IntersectionObserver(function (entries, obs) {
    entries.forEach(function (entry) {
      if (entry.isIntersecting) {
        entry.target.classList.add('is-visible'); // .is-visible défini dans section.css
        obs.unobserve(entry.target);
      }
    });
  }).observe(el);
}

Remplacez ID_DE_LA_SECTION par l'identifiant réel (visible dans le chemin sections/<page>/custom/<id>/) ou ciblez un enfant par sa propre classe CSS, plus robuste. Dans l'éditeur (aperçu visuel ou « Modifier le code »), la section s'affiche déjà visible dès le chargement — un IntersectionObserver s'y déclenche donc immédiatement ; ce n'est qu'en conditions réelles (boutique publique, section plus bas dans une page longue) que le déclenchement au défilement se manifeste vraiment.

Réutiliser du code — {{ include:'...' }}

Réutilise un fichier existant n'importe où ailleurs via {{ include:'chemin/exact/du/fichier.ext' }} plutôt que de dupliquer le même code à plusieurs endroits — fonctionne pour un fichier de snippets/, un assets/, ou un autre fichier d'une section personnalisée (jusqu'à 5 niveaux d'inclusions imbriquées ; une inclusion inconnue ou cyclique résout silencieusement vers une chaîne vide plutôt que de faire échouer la page).

Réglages sans coder — {{ params.x }}

N'importe où dans section.html, section.css ou section.js, écrire {{ params.maCle }} et déclarer maCle dans les params du meta.json (ou dans le bundle collé, ci-dessous) fait apparaître un contrôle réel — sélecteur de couleur, champ texte, liste déroulante… — directement dans l'onglet de la section de l'éditeur visuel. Le marchand n'a alors plus jamais besoin de rouvrir le code pour changer cette valeur.

Le type n'est PAS une liste fermée — n'importe quel nom de type est accepté, jamais rejeté. Ces 12 ont un contrôle visuel dédié dans l'éditeur (à privilégier, l'expérience du marchand est meilleure) ; un type inventé reçoit simplement un champ texte tant qu'aucun contrôle dédié n'existe pour lui — et s'il en gagne un plus tard, chaque section qui l'utilise en profite automatiquement, sans rien changer.

TypeContrôle affichéQuand l'utiliserExemple minimal
textChamp texteContenu court : titre, sous-titre, texte de bouton.{ "key": "titre", "type": "text", "default": "Mon titre" }
textareaZone de texteContenu long : description, plusieurs paragraphes.{ "key": "description", "type": "textarea", "default": "Un texte plus long." }
colorSélecteur couleur/dégradéCouleur de texte, de fond, d'accent ou de bordure.{ "key": "couleurFond", "type": "color", "default": "#ffffff" }
numberChamp numériqueTaille, espacement, largeur, nombre d'éléments — min/max/step optionnels.{ "key": "colonnes", "type": "number", "default": "3", "min": 1, "max": 6 }
rangeCurseur (slider)Comme number, en glissière — min/max/step (défaut 0/100/1).{ "key": "opacite", "type": "range", "default": "80", "min": 0, "max": 100 }
selectListe déroulantePolice, poids, alignement, style d'animation — tout choix dont une valeur libre pourrait ne rien produire. options obligatoire, toujours des valeurs concrètes.{ "key": "align", "type": "select", "default": "left", "options": [{"value":"left","label":"Gauche"}] }
booleanInterrupteur on/offValeur stockée "true"/"false" — testez-la ainsi dans le JS.{ "key": "afficherBouton", "type": "boolean", "default": "true" }
iconGrille d'icônesJeu curaté (réassurance/garantie) : "truck", "hand-coins", "phone", "rotate-ccw", "shield-check", "clock", "map-pin", "badge-check".{ "key": "icone", "type": "icon", "default": "truck" }
imageBouton d'uploadValeur stockée : l'URL résultante.{ "key": "photo", "type": "image", "default": "" }
dateSélecteur de dateValeur stockée en date ISO (AAAA-MM-JJ).{ "key": "dateFin", "type": "date", "default": "2026-12-31" }
urlChamp texte (type url)Même contrôle que text, nommé pour la clarté (un lien plutôt qu'un titre).{ "key": "lienBouton", "type": "url", "default": "/produits" }
repeaterListe réordonnable d'élémentsQuestions/réponses, avis, étapes — remplace les anciens slots fixes (question1, question2…) ou un data.json non éditable. subFields obligatoire (uniquement text/textarea/color/number/select, 10 maximum) ; la valeur est un tableau JSON sérialisé en chaîne (20 000 caractères maximum) — jamais collée directement entre guillemets dans section.js, voir le format sûr ci-dessous.{ "key": "avis", "type": "repeater", "default": "[]", "subFields": [{"key":"nom","type":"text","default":""}] }

Limite réelle actuelle : subFields n'accepte pas image (ni icon, ni repeater) — une galerie d'images éditable (une photo différente par élément d'une liste) n'est donc pas possible en un seul repeater aujourd'hui. Contournement selon le besoin : un nombre d'images FIXE et connu à l'avance → autant de paramètres image séparés (image1, image2…, chacun avec son vrai bouton d'upload) ; un nombre variable → un subFields text/url contenant l'URL de l'image (copiée-collée depuis un autre champ image déjà uploadé, faute d'un vrai bouton d'upload par ligne).

Pour une police (type select), seules ces valeurs sont réellement chargées sur la boutique — toute autre valeur ne s'affiche pas, le navigateur revient silencieusement à une police par défaut : "Poppins", "Playfair Display", "Merriweather", "Roboto", "Montserrat", "Lora", "Oswald", ou "inherit" (police générale du site — choix par défaut recommandé).

Pour une animation : pas de librairie externe — une vraie animation CSS (@keyframes + animation/transition dans section.css, éventuellement un IntersectionObserver simple dans section.js pour déclencher au défilement). Exposer le style en "select" (ex. "aucune", "fondu", "glissement-bas") et faire varier la classe/le style selon {{ params.animation }} plutôt que de générer plusieurs versions du fichier.

Coller une section entière en une fois

Sur sections/<page>/custom/, le « + » propose aussi « Coller une section » à côté de « Nouvelle section » — coller un objet JSON crée d'un coup l'identité de la section, tous ses fichiers, et tous ses paramètres modifiables. C'est le format à demander à une IA générative. Format exact :

{
  "name": "FAQ moderne",
  "files": {
    "section.html": "<div class=\"faq\">...</div>",
    "section.css": ":root { --faq-color: {{ params.accentColor }}; } .faq { font-family: '{{ params.police }}', sans-serif; }",
    "section.js": "console.log('faq prête');",
    "data.json": "{\"exemple\": true}"
  },
  "params": [
    { "key": "accentColor", "label": "Couleur d'accent", "type": "color", "default": "#6366f1" },
    { "key": "titre", "label": "Titre de la section", "type": "text", "default": "Questions fréquentes" },
    {
      "key": "police",
      "label": "Police du titre",
      "type": "select",
      "default": "inherit",
      "options": [
        { "value": "inherit", "label": "Police du site" },
        { "value": "Playfair Display", "label": "Playfair Display" },
        { "value": "Montserrat", "label": "Montserrat" }
      ]
    },
    { "key": "afficherIcones", "label": "Afficher les icônes", "type": "boolean", "default": "true" },
    { "key": "tailleTitre", "label": "Taille du titre", "type": "range", "default": "28", "min": 16, "max": 48, "step": 2 },
    {
      "key": "questions",
      "label": "Questions",
      "type": "repeater",
      "default": "[]",
      "value": "[{\"question\":\"Livrez-vous partout ?\",\"reponse\":\"Oui, dans tout le pays.\"}]",
      "subFields": [
        { "key": "question", "label": "Question", "type": "text", "default": "" },
        { "key": "reponse", "label": "Réponse", "type": "textarea", "default": "" }
      ]
    }
  ]
}

files est le seul champ obligatoire (extensions autorisées : .html/.css/.js pour le code exécuté, .json/.svg/.txt pour du support) ; name et params sont optionnels. section.html/.css/.js sont auto-inclus ; tout autre nom n'est accessible que via {{ include:'sections/<page>/custom/<id>/<nom>' }}. Sur un param, min/max/step ne s'appliquent qu'à number/range, options qu'à select, subFields qu'à repeater (tous restent silencieusement ignorés pour tout autre type). La valeur d'un repeater (ex. questions ci-dessus) est une chaîne JSON.

Échappement : {{ params.x }} est un remplacement de texte BRUT, identique partout (HTML, CSS, JS) — aucun échappement contextuel, aucune protection XSS automatique. Un caractère spécial HTML (<, >, &) dans un text/textarea/repeater est inséré tel quel dans le HTML rendu — prévenez le marchand de coller du texte brut dans ces champs, pas du contenu copié depuis un traitement de texte. Une key référencée mais jamais déclarée dans params se résout en chaîne vide silencieusement, sans erreur ni fuite du {{ }} littéral sur la boutique publique.

Ne jamais coller un repeater (ou tout text/textarea) directement entre guillemets dans section.js, par exemple JSON.parse('{{ params.questions }}') — une seule apostrophe dans le texte (« d'avaler », « l'utiliser »…) referme le guillemet prématurément et casse tout le fichier silencieusement (rien ne s'affiche, aucune erreur visible). Passez par un <script type="application/json"> dans section.html, sans risque puisqu'il est lu comme du texte, jamais interprété comme du JS :

<!-- section.html -->
<script type="application/json" data-questions>{{ params.questions }}</script>

<!-- section.js -->
const el = document.querySelector('[data-questions]');
const questions = JSON.parse(el.textContent);
questions.forEach(q => { /* q.question, q.reponse */ });

Une section qui viole cette règle est refusée à l'enregistrement — « Coller une section » renvoie une erreur expliquant quoi corriger.

Erreurs à ne jamais faire (liste de contrôle)

Ce sont les causes réelles, observées, de sections collées qui « ne marchent pas » — à vérifier avant de considérer un JSON généré par une IA comme fini :

  1. Un {{ params.x }} entre les mêmes guillemets dans section.js, section.css, ou un attribut HTML (value="{{ params.x }}") — voir l'encart ci-dessus. La cause la plus fréquente d'une section qui s'affiche vide sans erreur visible.
  2. Lire un repeater avec .value sur autre chose qu'un champ de formulaire (<script>, <div>…) — .value y vaut toujours undefined. .textContent avec <script type="application/json">, ou .value avec un vrai <textarea hidden> — jamais l'inverse.
  3. Un paramètre déclaré dans params mais jamais utilisé dans les fichiers — le marchand voit un réglage qui ne change visuellement rien.
  4. Une valeur testée en dur dans le JS qui ne correspond pas exactement à une option réellement déclarée (casse, accent, mot différent).
  5. Des sélecteurs CSS/JS trop génériques (.card, #grid) — risque de collision avec une autre section ou le CSS du site. Préfixez toujours vos classes.
  6. Du JS qui suppose une bibliothèque externe (jQuery, une lib d'animation) — rien n'est chargé sur la boutique, JS/CSS pur uniquement.
  7. Un script qui remplit son conteneur sans le vider d'abord (appendChild sans innerHTML = '' avant la boucle) — dédouble le contenu si le script s'exécute deux fois.
  8. Un élément de texte sans règle CSS pour disparaître quand il est vidé — obligatoire pour CHAQUE élément affichant un text/textarea, sans exception, jamais seulement pour les titres. N'importe quel champ peut être vidé par le marchand (rien n'est jamais « trop important pour être vidé » de son point de vue) : vider un champ donne bien <h2 class="ma-section-title"></h2> (balise vide), mais tant que le CSS ne prévoit rien, elle garde sa taille de police et sa marge — une zone blanche reste à la place du texte disparu (cas réel : une section où le titre, le sous-titre ET la mention légale avaient tous les trois ce trou). Pour CHAQUE élément de ce type — titre, sous-titre, kicker, légende, mention légale, ET tout élément rempli dynamiquement par section.js à partir d'un repeater (ex. nameEl.textContent = item.nom || '' peut produire un élément vide) — ajoutez dans section.css une règle dédiée : .ma-section-title:empty{display:none} — ne cible l'élément que quand il est réellement vide, donc sans effet tant qu'il y a du texte. Avant de considérer un JSON final, listez chaque paramètre text/textarea ET chaque subFields d'un repeater, et vérifiez qu'une règle :emptycorrespondante existe pour l'élément qui l'affiche. (Un champ volontairement vidé est bien rendu comme une vraie chaîne vide, jamais remplacé par default.)
  9. Un section.js qui rend CHAQUE élément d'un repeater, y compris ceux sans contenu réel encore. Un item fraîchement ajouté a tous ses subFields vides jusqu'à ce que le marchand tape quelque chose — un items.forEach(...) sans filtre affiche alors une carte quasi vide (icône seule, texte vide), pas juste un texte manquant que :empty suffirait à corriger (l'erreur précédente traite un ÉLÉMENT DE TEXTE vide, celle-ci un ITEM DE REPEATER entièrement vide). Filtrez avant la boucle de rendu : items.filter(it => Object.values(it).some(v => String(v||'').trim())). Un tableau qui finit vide après ce filtre doit produire une section qui n'affiche rien du tout.
  10. Le padding vertical de chaque section, additionné sans y penser sur plusieurs sections empilées. 56px en haut/bas semble raisonnable pour UNE section isolée, mais 3-4 sections personnalisées à la suite cumulent facilement 300-400px d'espace vide entre elles, surtout visible sur mobile. Recommandation : 48-72px desktop pour une section isolée (moitié moins sur mobile) — réduisez délibérément dès que vous savez que la section sera collée juste après une autre section personnalisée déjà en place.
  11. La couleur de FOND de la section codée en dur, sans paramètre. Le réglage manquant le plus fréquent en pratique — le marchand n'y pense pas toujours spontanément, mais en a besoin dès la première déclinaison saisonnière ou promo. Vérifiez explicitement qu'un paramètre color pilote le fond de la section elle-même, pas seulement les couleurs de contenu à l'intérieur.

Socle quasi universel de réglages

Presque AUCUNE section n'y échappe — à inclure par défaut même si le marchand ne les mentionne pas explicitement (ne les omettre que si l'un d'eux n'a authentiquement aucun sens pour cette section précise) :

  • Couleur de fond de la section elle-même
  • Couleur du texte
  • Couleur d'accent (boutons/liens/icônes)
  • Padding vertical de la section (voir la recommandation ci-dessus)
  • Arrondi des coins des blocs/cartes
  • Un style d'animation d'apparition (avec au moins une option « aucune »)

Demande à plusieurs variantes — rester compact

Une demande qui énumère plusieurs variantes d'une même chose (« 7 animations », une mise en forme par ligne, une liste d'éléments) tente naturellement de générer un bloc quasi identique par variante — ça marche, mais gonfle inutilement la taille de la réponse (risque réel de dépasser une limite de longueur en cours de génération, et coût direct si vous êtes vous-même une IA facturée par token). Écrivez du code FACTORISÉ, PILOTÉ PAR DONNÉES, jamais un bloc dupliqué par variante :

  • Une seule famille de classes CSS pilotée par un attribut de données ([data-anim="fondu"], [data-anim="glissement"]…) plutôt que des @keyframes nommés en double pour un rendu presque identique.
  • Une seule fonction JS paramétrée par le nom/la config de la variante plutôt qu'une fonction quasi identique par variante.
  • Un repeater pour « plusieurs lignes/éléments ajoutables » plutôt que des paramètres à slots fixes (ligne1, ligne2…) qui ne couvrent qu'un nombre deviné à l'avance.

Le tableau params n'est JAMAIS la variable d'ajustement pour tenir dans une réponse plus courte — une section un peu plus simple mais réellement réglable vaut toujours mieux qu'une section riche mais codée en dur. S'il faut vraiment réduire quelque chose pour tenir en une seule réponse, réduisez le nombre de variantes proposées (ex. 3-4 styles bien choisis plutôt que les 7 demandés) et dites-le explicitement au marchand plutôt que de deviner silencieusement.

Composition entre sections empilées

Chaque exemple ci-dessus montre une section isolée — en pratique une page en empile plusieurs (fixes ET personnalisées) directement les unes sous les autres, sans espace ajouté par Ordelyx entre elles. Deux effets à anticiper, pas seulement corriger après coup :

  • Padding cumulatif — voir la liste de contrôle ci-dessus. En générant plusieurs sections pour la même page dans la même conversation, gardez un padding cohérent et plutôt resserré entre elles.
  • Contraste entre sections voisines — deux sections à la même couleur de fond, l'une après l'autre, fusionnent visuellement sans qu'on distingue où l'une finit. Alternez légèrement les fonds, ou ajoutez une bordure/ombre discrète en haut de la nouvelle section.

Accessibilité

À traiter comme un standard du rendu, pas une option séparée :

  • Accordéon (FAQ, détails produit) : le bouton qui contrôle l'ouverture porte aria-expanded="true"/"false" (mis à jour en JS à chaque clic) et aria-controls="id-du-panneau" pointant vers l'id réel du contenu déplié.
  • Image dans un repeater (ou toute image dont la légende vient d'un champ du marchand) : alt="{{ ce champ }}" — un alt vide n'est acceptable que pour une image purement décorative, jamais une photo produit/témoignage/ingrédient.
  • Contraste texte/fond : pour des couleurs LITTÉRALES choisies par vous (pas encore personnalisées par le marchand), visez un ratio lisible (WCAG AA, ≥ 4.5:1) — gris clair sur blanc et texte à faible opacité sur un accent vif sont les deux erreurs les plus fréquentes.

Expliquer au marchand où coller le résultat

Ne vous arrêtez jamais à « voici le code » — terminez toujours par des instructions numérotées, en français simple, sans supposer la moindre connaissance technique. Un marchand qui ne sait pas coder doit pouvoir suivre ces étapes sans se poser une seule question (adaptez la page et la position à la demande réelle — jamais un espace réservé du type « <nom-de-la-page> » dans la réponse finale) :

  1. Dans le tableau de bord Ordelyx, ouvrir Boutique > Thèmes.
  2. Sur le thème actif, cliquer sur « Modifier le code ».
  3. Dans la colonne de gauche, ouvrir sections, puis la page concernée (ex. accueil), puis custom.
  4. Survoler le dossier custom et cliquer sur le petit « + » qui apparaît.
  5. Choisir « Coller une section » (pas « Nouvelle section »).
  6. Coller exactement le bloc JSON généré — tout, accolades {/} incluses.
  7. Choisir où l'insérer avec le menu « Insérer après : … », juste en dessous.
  8. Cliquer sur « Importer » — la section apparaît immédiatement, fichiers et paramètres déjà créés.

Raccourci pour compléter une section FIXE précise (Bannière, Réassurance…) sans chercher le dossier custom/ : son propre onglet dans l'éditeur visuel (pas « Modifier le code ») a un bouton « Ajouter un complément à cette section » tout en bas — à mentionner quand la demande porte sur l'enrichissement d'une section fixe existante plutôt que l'ajout d'une section entièrement nouvelle.

Pour REMPLACER une section fixe plutôt que la compléter : le même onglet a un bouton « Convertir en section personnalisée » — il crée une vraie section personnalisée pré-remplie avec l'équivalent du rendu actuel de la section fixe, à la même position dans l'ordre, et désactive la section fixe d'origine. Point de départ éditable à 100 % ensuite, sans aucune limite de structure.

Supprimer une section personnalisée

L'icône corbeille de son dossier sections/<page>/custom/<id>/ la supprime vraiment (dossier et tous ses fichiers) — contrairement à une section fixe, il n'existe pas de simple « désactivée ». Retirer sa seule référence de l'ordre de la page ne suffit pas à la supprimer, juste à la sortir de la page.

Limites réelles

  • Un fichier (section.html/.css/.js, un asset, un snippet) : 200 000 caractères maximum.
  • Nombre de fichiers : 200 maximum par catégorie et par thème — assets/, snippets/, et l'ensemble des fichiers de TOUTES vos sections personnalisées (toutes pages confondues) forment chacun leur propre compteur de 200.
  • Une section : 30 params maximum.
  • Un paramètre "select" : 30 options maximum.
  • Un paramètre "repeater" : 10 subFields maximum, valeur sérialisée 20 000 caractères maximum au total (pas de plafond strict sur le nombre d'éléments en soi — l'éditeur visuel arrête juste d'en proposer de nouveaux au-delà de 30 par prudence).
  • Une clé de paramètre (key) : 60 caractères. Un label : 80 caractères. Un default/value : 2 000 caractères (20 000 pour un "repeater").

Garde-fous contre une croissance de stockage incontrôlée, pas des plafonds qu'une vraie boutique atteint en usage normal — aucun ne rejette silencieusement : un dépassement est soit refusé avec un message clair (fichiers, taille, .zip), soit tronqué proprement (texte, options, éléments de repeater).

8. Code global du site

assets/site.html, assets/site.css et assets/site.js — le code qui s'applique à toute la boutique, pas à une section précise. Reproduction exacte de la documentation montrée dans l'éditeur pour ces trois champs :

# Personnalisation de code — boutique Ordelyx (thème "Franc")

Ce code (HTML, CSS, JS) s'exécute UNIQUEMENT sur la boutique publique de
cette boutique (son sous-domaine ou domaine personnalisé) — jamais sur le
tableau de bord Ordelyx, jamais sur une autre boutique. Vous êtes seul(e)
responsable de ce que vous ajoutez ici.

## Où ça s'exécute
- Le CSS est injecté dans une balise <style> propre à cette page.
- Le HTML est injecté dans un conteneur <div id="ordelyx-custom-html">,
  juste avant la fin de la page (après le pied de page). Pour positionner
  ce contenu ailleurs à l'écran, utilisez CSS (position: fixed/absolute)
  ou JavaScript (déplacement du DOM) — ne vous attendez pas à ce qu'il
  s'intercale automatiquement entre les sections existantes.
- Le JS s'exécute comme un <script> classique, une fois la page chargée.

## Variables CSS disponibles (déjà utilisées partout sur le site)
--background, --foreground, --surface, --surface-muted, --border-subtle,
--color-accent-50, --color-accent-100, --color-accent-500,
--color-accent-600, --color-accent-700, --color-accent-foreground,
--color-success, --color-danger, --color-warning,
--radius-sm, --radius-md, --radius-lg, --radius-xl, --radius-2xl,
--font-sans, --font-mono, --font-display.
Exemple : "background: var(--color-accent-600); border-radius: var(--radius-lg);"
--color-accent-500/600/700 changent déjà selon la couleur d'accent choisie
par le marchand (Réglages du thème) — les réutiliser garde vos ajouts
cohérents avec le reste de la boutique automatiquement.

## Classes Tailwind
Tailwind CSS est chargé globalement — les classes utilitaires standard
(ex. "flex", "rounded-xl", "text-sm", "gap-4") fonctionnent directement
dans le HTML personnalisé.

## Ce qu'il ne faut jamais faire ici (sécurité — responsabilité de la plateforme)
- N'essayez PAS d'appeler les API internes d'Ordelyx (tout ce qui commence
  par /api ou passe par les Server Actions du tableau de bord) — ce code
  tourne sur le site public, sans la session du marchand, ça ne
  fonctionnera pas et ce n'est pas destiné à ça.
- Ne lisez/écrivez pas de cookies visant app.ordelyx.app — ce sous-domaine
  est une origine différente, le navigateur bloque déjà cet accès.
- N'insérez pas de <script src="..."> venant d'un domaine auquel vous ne
  faites pas confiance : il s'exécuterait avec les mêmes droits que ce
  code, sur cette page-ci uniquement (jamais sur les autres boutiques ni
  sur le tableau de bord — l'isolation par sous-domaine du navigateur s'en
  charge), mais reste vecteur d'attaque possible contre les visiteurs de
  CETTE boutique. Ne collez du code que si vous en connaissez la source.
- Le formulaire de commande (checkout) a sa propre protection anti-fraude
  et ses propres champs, réglables depuis "Formulaire COD" — ne tentez pas
  de le reconstruire ou de l'intercepter en JavaScript ici.

## Ce que vous pouvez faire librement
Ajouter des balises meta, des scripts d'analytics tiers, des widgets de
chat, des animations, des styles personnalisés poussés, du contenu HTML
additionnel, des interactions JavaScript propres à cette boutique.

9. Construire un thème 100% personnalisé — méthode

Pour reproduire cette architecture depuis zéro (ou modifier en profondeur un thème dupliqué) :

  1. Dans Boutique > Thèmes, dupliquer le thème actif (« ⋯ » > « Dupliquer ») — ne jamais modifier « Franc » directement tant que le résultat n'est pas validé. Ouvrir la copie > « Modifier le code ».
  2. Pour chaque fichier géré par Ordelyx (§3, §4, §5) : remplacer les valeurs par celles voulues — des clés en plus de celles montrées plus haut sont conservées (elles ne sont juste pas encore lues par l'affichage), seul un JSON syntaxiquement invalide est refusé à l'enregistrement.
  3. Pour tout ce que les sections fixes ne couvrent pas : créer autant de sections personnalisées que nécessaire (§7), sur la bonne page, à la bonne position, avec des params pour chaque valeur que le marchand doit pouvoir ajuster ensuite sans coder.
  4. Réordonner templates/index.json / templates/produit.json (ou l'ordre du Catalogue/d'une page perso) pour placer chaque section fixe et personnalisée exactement où elle doit apparaître.
  5. Ajouter du code sitewide (§8) uniquement pour ce qui doit s'appliquer à toute la boutique.
  6. Utiliser le panneau « Aperçu » (fichier en direct + page complète publiée) pour vérifier chaque section avant de publier le thème (« ⋯ » > « Publier » depuis Boutique > Thèmes).
  7. Une fois satisfait, exporter le résultat en .zip (§10) — pour le sauvegarder, le dupliquer vers une autre boutique, ou le partager.

10. Exporter / Importer un thème (.zip)

Boutique > Thèmes propose « Exporter (.zip) » sur le menu « ⋯ » de chaque thème, et un bouton « Importer un thème (.zip) » au-dessus de la grille. Ce n'est pas un format inventé pour l'occasion : un export est exactement la même donnée que « Dupliquer » copie déjà entre deux thèmes de la même boutique — juste sérialisée dans un fichier portable au lieu d'une copie base de données à base de données. C'est pourquoi un thème importé se comporte immédiatement, en tout point, comme un thème dupliqué : brouillon, modifiable depuis « Modifier le code », publiable, supprimable, ré-exportable — sans aucun traitement spécial ailleurs dans la plateforme. Le thème d'origine (« Franc » ou tout autre) n'est jamais modifié par un export ni par un import.

  • Archive .zip uniquement, 10 Mo maximum.
  • Un thème étant entièrement du texte (JSON/HTML/CSS/JS — aucune image n'est jamais intégrée dans l'archive, seulement des URL vers le stockage de la boutique d'origine), cette limite est très largement suffisante pour n'importe quel thème réel.
  • À la racine de l'archive : ordelyx-theme.json (le seul fichier relu à l'import) — un dossier files/ reflète aussi chaque fichier réel en clair, uniquement pour consultation (ouvrir l'archive, donner un seul fichier à une IA) : il n'est jamais relu à l'import.
  • Une entrée invalide (chemin, type ou taille non conformes aux mêmes règles que §7) est ignorée individuellement plutôt que de faire échouer tout l'import — le nombre de fichiers ignorés est affiché après l'import.
  • Un favicon ou une image référencée dans le thème importé reste une URL absolue vers le stockage de la boutique d'origine — l'image continue de s'afficher, mais reste hébergée là-bas tant qu'elle n'est pas remplacée manuellement.

Format exact du manifeste (ordelyx-theme.json) :

{
  "ordelyxTheme": 1,
  "name": "Franc (copie)",
  "exportedAt": "2026-09-01T10:00:00.000Z",
  "settings": {
    "announcement_settings": { /* ... même forme que layout/annonce.json */ },
    "header_settings": { /* ... même forme que layout/en-tete.json */ },
    "home_sections": { /* ... 15 sections + customBlocks + order, voir §4 */ },
    "product_page_settings": { /* ... 16 sections + customBlocks + order, voir §5 */ },
    "catalog_sections": { "order": ["productGrid"], "customBlocks": [] },
    "whatsapp_settings": { /* ... */ },
    "contact_section_settings": { /* ... */ },
    "footer_settings": { /* ... */ },
    "favicon_url": null,
    "custom_code_html": "",
    "custom_code_css": "",
    "custom_code_js": ""
  },
  "files": [
    { "path": "sections/accueil/custom/9f2b.../section.html", "kind": "section", "content": "<div>...</div>" },
    { "path": "assets/animations.css", "kind": "asset", "content": "@keyframes fade { ... }" }
  ],
  "folders": [
    { "path": "sections/accueil/custom/9f2b.../icons" }
  ]
}

settings reprend exactement les mêmes formes que §3/§4/§5/§6 (mêmes clés que layout/*.json, sections/accueil/*.json, sections/fiche-produit/*.json, et les anchors de §6) — files/folders reprennent les mêmes chemins que l'arborescence (§2, §7) avec kind{ asset, snippet, section }. Une valeur manquante ou d'une forme inattendue dans settings est remplacée par sa valeur par défaut plutôt que de faire échouer l'import — le nom du thème est repris de celui choisi lors de l'import, ou à défaut de name dans le manifeste.

11. Notes pour une IA qui lit cette page

Cette page est la référence complète et à jour — l'éditeur lui-même n'affiche qu'un résumé du mécanisme (README.md de l'arborescence, bouton « Copier le README »), sans les valeurs par défaut exactes listées ici. Avant de générer une section pour un marchand, ne jamais deviner à partir d'une demande vague — des questions superficielles (« quelle couleur ? ») donnent un résultat générique qui aurait pu convenir à n'importe quelle boutique. Poser des questions qui touchent au fond du sujet, pas seulement à sa forme — en une fois, sous forme de liste courte :

  1. Objectif et public — la plus importante, en premier : à quoi sert vraiment cette section (rassurer, créer de l'urgence, expliquer le produit, donner envie d'acheter) ? À qui s'adresse la boutique ? Une FAQ pour du cosmétique haut de gamme et une FAQ pour des accessoires téléphone pas chers n'ont ni le même ton, ni la même mise en page — deviner l'une pour l'autre produit un résultat hors sujet même si le HTML fonctionne.
  2. Contenu réel (jamais de fausses données définitives sans l'annoncer comme tel).
  3. Emplacement exact : quelle page, à quel endroit dans l'ordre.
  4. Apparence modifiable — viser l'exhaustivité, pas 2-3 réglages évidents : parcourir les 12 types du §7 un par un et se demander, pour chaque élément visuel de la section (couleur, texte, image, espacement, mise en page, comportement), s'il mérite un param plutôt que d'être codé en dur — le but est que le marchand n'ait jamais besoin de rouvrir le code après coup. Socle quasi universel à inclure par défaut (voir §8) : couleur de fond de la section — le plus souvent oublié en pratique —, couleur du texte, couleur d'accent, padding vertical, arrondi des coins, style d'animation. Ce tableau params n'est jamais la variable d'ajustement pour raccourcir une réponse (voir §7 sur les demandes à plusieurs variantes) — réduisez plutôt le nombre de variantes proposées.
  5. Cohérence de marque : couleur d'accent et police déjà utilisées, ou préférence différente ; le style des photos déjà en ligne doit-il influencer la mise en page ?
  6. Comportement : interactivité nécessaire (accordéon, carrousel, compte à rebours…) — toujours en CSS/JS pur (§7), jamais une librairie externe supposée.
  7. Qualité visuelle — non négociable, jamais une question, toujours un standard : un rendu moderne et soigné, jamais générique ni daté. Espacements généreux, vraie hiérarchie typographique, coins arrondis cohérents (var(--radius-lg) etc., §8), ombres légères si utilisées, contraste suffisant. À éviter : dégradés violet/bleu par défaut sans lien avec la marque, icônes disproportionnées, blocs de texte non aérés. Toujours pensé mobile en premier.

Produire ensuite le bundle exact du §7 (« Coller une section »), avec des params pour chaque valeur raisonnablement variable. Terminer toujours par des instructions numérotées en français simple, disant très précisément où le coller (voir « Expliquer au marchand où coller le résultat », §7) — jamais seulement « voici le code ».

Ordelyx cible des marchands francophones d'Afrique — le contenu réel généré sera quasi toujours en français. Testez systématiquement vos exemples avec du texte contenant de vraies apostrophes françaises (« d'avaler », « l'utiliser »), des accents, et des guillemets français (« » — U+00AB/U+00BB), pas seulement de l'anglais ou du franglais sans ponctuation piégeuse. Un exemple qui fonctionne avec « hello world » mais casse sur « l'huile d'argan » n'a rien vérifié d'utile.