#La coquille
layout/theme.liquid est le HTML qui entoure toutes les pages du site : le
doctype, la balise head, l’en-tête, le pied, et le trou dans lequel le
contenu de la page vient tomber. C’est le seul fichier de layout/ que le
moteur connaisse, et l’un des trois sans lesquels un thème ne rend rien.
Cette page le dissèque à partir de themes/origo/layout/theme.liquid, qui
tient en quatre-vingt-dix-huit lignes et fait tout ce qu’une coquille doit
faire.
#Le fichier entier
<!doctype html>
{%- comment -%}
ORIGO — la coquille.
Les réglages deviennent des variables CSS, ici et nulle part ailleurs.
C'est ce qui permet de tout retoucher depuis l'éditeur sans qu'une seule
section ait à connaître une couleur : elles héritent toutes de `:root`.
Deux choses ne PEUVENT PAS se dire avec une variable, et sortent donc en
attribut sur `<html>` :
· l'ÉDITION — les signatures d'un habillage (barre d'accent devant les
titres, filet doublé sous l'en-tête, cadres, ombres, interlettrage)
tiennent à des règles entières, pas à un nombre ;
· rien d'autre. Tout le reste passe par `:root`, et c'est voulu : un
attribut de plus, c'est une combinaison de plus à vérifier.
Le thème NOMME une police, la plateforme la sert : `assets/` n'accepte
que `.css` et `.svg`, donc aucun fichier de police ne peut y vivre.
Quand aucune police n'est choisie, on retombe sur la pile système — un
thème de départ doit rendre même vide.
{%- endcomment -%}
<html lang="fr" data-edition="{{ settings.edition | default: 'origo' }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ page.titre }} — {{ site.nom }}</title>
<meta name="theme-color" content="{{ settings.papier | default: '#ffffff' }}">
{{ 'theme.css' | asset_url | stylesheet_tag }}
<style>
{{ settings.police_titres | font_face }}
{%- comment -%}
Origo sort de sa boîte avec la MÊME police partout. Émettre deux
fois la règle ferait télécharger le fichier une fois — les
navigateurs dédoublonnent — mais l'écrirait deux fois dans le HTML
de chaque page, ce qui se voit à la lecture de la source et donne
l'air d'un thème mal réglé.
{%- endcomment -%}
{%- if settings.police_texte != settings.police_titres -%}
{{ settings.police_texte | font_face }}
{%- endif -%}
</style>
<style>
{%- assign pile_titres = settings.police_titres | font_famille -%}
{%- assign pile_texte = settings.police_texte | font_famille -%}
:root {
--encre: {{ settings.encre }};
--papier: {{ settings.papier }};
--voile: {{ settings.voile }};
--accent: {{ settings.accent }};
--accent-texte: {{ settings.accent_texte }};
--accent-sombre: {{ settings.accent | teinte: -18 }};
--accent-clair: {{ settings.accent | teinte: 88 }};
--largeur: {{ settings.largeur }}px;
--arrondi: {{ settings.arrondi }}px;
--echelle: {{ settings.echelle }};
--densite: {{ settings.densite }};
/* L'épaisseur des filets. Un seul nombre pour tous les traits du
thème : sans lui, épaissir la séparation de l'en-tête laisserait
le pied et la FAQ en cheveu, et le site paraîtrait mal réglé. */
--filet: {{ settings.filets | default: 1 }}px;
/* La forme des boutons. `droit` vaut ZÉRO et non « presque zéro » :
c'est la seule valeur qui se voit sur une vignette. */
--bouton-rayon: {% case settings.boutons %}{% when 'droit' %}0px{% when 'doux' %}8px{% else %}999px{% endcase %};
/* Les pastilles suivent les boutons — numéros d'étape, étiquettes de
forfait, jetons. Un bouton carré au-dessus d'un numéro rond fait
deux thèmes sur la même page. */
--pastille: var(--bouton-rayon);
--texte-police: {% if pile_texte != blank %}{{ pile_texte }}{% else %}ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif{% endif %};
--titre-police: {% if pile_titres != blank %}{{ pile_titres }}{% else %}var(--texte-police){% endif %};
--titre-casse: {% if settings.casse_titres == 'capitales' %}uppercase{% else %}none{% endif %};
/* Un titre en capitales serré au crénage négatif se referme sur
lui-même : la valeur change AVEC la casse, jamais séparément. */
--titre-espace: {% if settings.casse_titres == 'capitales' %}0.005em{% else %}-0.022em{% endif %};
--surtitre-casse: {% if settings.casse_surtitres == 'normale' %}none{% else %}uppercase{% endif %};
/* L'interlettrage d'un surtitre suit sa casse, jamais l'inverse :
les 0,12 em qui aèrent DES CAPITALES écartèlent un mot en bas de
casse, qu'on lit alors lettre à lettre. */
--surtitre-espace: {% if settings.casse_surtitres == 'normale' %}0.015em{% else %}0.12em{% endif %};
}
</style>
</head>
<body>
<a class="saut" href="#contenu">Aller au contenu</a>
{% section 'annonce' %}
{% section 'entete' %}
<main id="contenu">{{ content_for_layout }}</main>
{% section 'pied' %}
</body>
</html>La dissection qui suit ne reprend pas tout : elle s’arrête à
{{ content_for_layout }}, au titre, à la feuille de style, aux variables
CSS, au lien d’évitement et à {% section %}. Les filtres font_face et
font_famille sont détaillés dans Filtres, et le réglage
edition dans Ajouter un thème au catalogue.
#{{ content_for_layout }} — le seul point obligatoire
Le moteur rend d’abord toutes les sections du template, concatène leur HTML,
puis rend la coquille avec cette chaîne dans content_for_layout. C’est la
mécanique de Shopify, et elle a l’avantage de laisser la coquille décider où
le contenu tombe et ce qui l’entoure.
Une coquille sans {{ content_for_layout }} rend une page sans contenu.
Sans erreur, sans avertissement : l’en-tête et le pied s’affichent, le milieu
est vide. Si une page paraît « ne rien afficher », c’est la première ligne à
chercher.
content_for_header n’existe pas dans Webcosa. Chez Shopify, il injecte
les balises de la plateforme dans le head. Ici il n’y a rien à injecter, et
la variable n’est pas définie : elle rendra une chaîne vide, silencieusement,
puisque le moteur tourne avec strictVariables désactivé. Ne le copie pas
depuis un thème Shopify en croyant qu’il fait quelque chose.
#Le head
#Le titre
<title>{{ page.titre }} — {{ site.nom }}</title>page.titre est le titre de la page courante, site.nom celui de
l’entreprise. Les huit thèmes d’origine utilisent exactement cette forme.
Voir Objets.
Sur le site en ligne, ce titre est remplacé : la plateforme retire le
<title>, le <meta charset> et le viewport de la coquille et pose les
siens dans le vrai <head> — le titre de référencement que le client a
réglé, sinon « Page — Site ». Le tien sert à l’aperçu et aux outils qui
lisent la coquille seule. Garde-le : il ne coûte rien et ne s’affiche
jamais en double.
#Ce que la plateforme pose pour toi
Tout ce qui suit est émis par Webcosa sur chaque page de chaque site, quel que soit le thème. Ta coquille ne doit pas le répéter — deux descriptions, deux canoniques ou deux graphes se contredisent, et les moteurs choisissent alors au hasard.
| Balise | Ce que la plateforme y met |
|---|---|
<title> | le titre de référencement de la page, sinon « Page — Site » |
meta description | celle de la page, sinon celle du site, sinon le premier texte de la page (155 signes) |
link rel="canonical" | l’adresse absolue sur l’hôte principal du site — son domaine vérifié, sinon <slug>.webcosa.site |
meta robots | index, follow (avec max-image-preview:large), ou noindex si le client refuse l’indexation |
og:*, twitter:* | titre, description, adresse, et toujours une image : celle de la page, du site, la photo du bandeau, ou une carte fabriquée au nom du site |
| icônes | l’icône réglée par le client, sinon ses initiales à la couleur d’accent du thème |
application/ld+json | un seul graphe : l’entreprise (LocalBusiness si elle a une adresse, Organization sinon), le site (WebSite), la page, son fil d’Ariane, la FAQ si une section en montre au moins deux questions, l’article sur le blog |
robots.txt, sitemap.xml | par site, sur l’hôte principal ; vides si le site est protégé, plan vide s’il refuse l’indexation. Les robots d’IA passent, sauf ceux que le client ferme depuis l’écran Agentique du CMS |
llms.txt | par site : sa fiche (métier, adresse, téléphone, horaires), la date de sa dernière mise à jour, les questions-réponses que le client a validées dans l’écran Agentique, ses pages publiées et ses articles, aux adresses canoniques ; 404 si le site est protégé, masqué des moteurs ou sans page publiée |
Ce qui reste à ta charge, et que la plateforme ne peut pas deviner : un
seul <h1> par page, une hiérarchie de titres sans saut, un alt sur
les images que tu poses toi-même (les photos de la médiathèque reçoivent
celui que le client a écrit), des dimensions sur chaque <img>.
La description de repli et le graphe lisent tes sections : le premier
réglage de texte courant (texte, chapeau, intro, description…),
les réglages de type image, les blocs question / reponse d’une FAQ,
le logo et les adresses de profils (instagram, facebook…) de
l’en-tête et du pied. Nomme tes réglages ainsi et ton thème en profite
sans une ligne de plus.
#La feuille de style
{{ 'theme.css' | asset_url | stylesheet_tag }}Deux filtres à la suite. asset_url transforme un nom de fichier en
adresse servie, stylesheet_tag l’enveloppe dans une balise link. Le
résultat, pour un thème d’identifiant abc… :
<link rel="stylesheet" href="/theme-assets/abc123.../theme.css">asset_url prend le nom seul, jamais le chemin. L’adresse produite est
/theme-assets/<id-du-thème>/<nom> — un seul segment de nom — et c’est la
route qui remet le préfixe assets/ pour interroger la base.
Écrire {{ 'assets/theme.css' | asset_url }} fabrique une adresse à deux
segments qui ne correspond à aucune route. La feuille de style répond 404, et
la page rend quand même : du HTML nu, mais du HTML. Un site sans CSS ressemble
à un thème mal écrit, pas à une adresse fausse — c’est pour ça que le défaut a
tenu longtemps, sur tous les sites à la fois.
Voir Assets pour le détail de la route et de son cache, et Filtres pour les autres filtres maison.
#Les réglages posés en variables CSS
C’est la technique des huit thèmes d’origine, et elle mérite d’être copiée : les
réglages globaux deviennent des variables CSS sur :root, ici et nulle
part ailleurs.
<style>
:root {
--encre: {{ settings.encre }};
--accent: {{ settings.accent }};
--accent-sombre: {{ settings.accent | teinte: -18 }};
--largeur: {{ settings.largeur }}px;
}
</style>Aucune section n’a alors besoin de connaître une couleur : elles héritent
toutes de :root, et la feuille de style dans assets/ reste un fichier CSS
ordinaire, sans une ligne de Liquid.
Le filtre teinte évite de multiplier les réglages : teinte: -18 assombrit
de 18 %, teinte: 88 éclaircit de 88 %. Sans lui, chaque nuance d’une
couleur réglable devrait être un réglage de plus, et un panneau de vingt
sélecteurs de couleur ne se remplit jamais.
Les unités s’écrivent hors de l’expression : {{ settings.largeur }}px
et non {{ settings.largeur | append: 'px' }}. Le réglage reste un nombre,
donc comparable et utilisable dans un {% if %}, et le CSS reste lisible.
Une condition Liquid peut aussi produire directement une valeur CSS :
--bouton-rayon: {% case settings.boutons %}{% when 'droit' %}0px{% when 'doux' %}8px{% else %}999px{% endcase %};C’est ce que fait origo pour la forme des boutons, la police des titres et
la casse des surtitres. Un select dans le schéma, une variable CSS dans la
coquille, et toute la feuille de style suit.
#Le body
#Le lien d’évitement
<a class="saut" href="#contenu">Aller au contenu</a>Premier élément focalisable de la page, visuellement masqué jusqu’à ce qu’il
reçoive le focus. Les huit thèmes le posent, et l’ancre #contenu
correspond au <main id="contenu"> plus bas. Ce n’est pas une exigence du
format, c’est une exigence d’accessibilité — et il coûte une ligne.
#{% section %} — l’en-tête et le pied
{% section 'annonce' %}
{% section 'entete' %}
<main id="contenu">{{ content_for_layout }}</main>
{% section 'pied' %}Le tag {% section 'nom' %} rend sections/nom.liquid depuis la
coquille, c’est-à-dire hors de tout template. Sans lui, il faudrait écrire
l’en-tête et le pied en dur ici, donc les sortir de l’éditeur et du schéma :
le client ne pourrait plus changer son numéro de téléphone sans toucher au
code.
Ce que la section reçoit :
section.idchaîneLe nom passé au tag. {% section 'entete' %} donne entete.
section.typechaîneLe même nom. Pour une section de coquille, id et type sont identiques —
il n’y a qu’une instance par nom.
section.settingsobjetLes valeurs lues dans config/settings_data.json, sous
current.sections.<nom>.settings, par-dessus les default du schéma de
la section.
section.blockstableauLes blocs lus sous current.sections.<nom>.blocks, ou un tableau vide.
Pour une section de coquille, blocks est passé tel quel : ce doit donc
être un tableau d’objets { "type": …, "settings": … } dans
settings_data.json, et non l’objet indexé par identifiant qu’utilisent les
templates. C’est la seule asymétrie du format, et elle n’est écrite nulle
part ailleurs. Voir Les blocs.
Une section absente ne casse rien : le tag rend un commentaire HTML
<!-- section absente : nom -->. Une section qui lève une erreur rend
<!-- section nom : message -->. Et l’imbrication est bornée à six niveaux,
compteur hors de portée du thème — détails dans
Le bac à sable.
#Ce que la coquille voit, et ce qu’elle ne voit pas
La coquille est rendue avec le contexte complet : site, page,
settings, menu, articles, annee, theme_base, plus
content_for_layout.
Elle ne voit pas section — il n’y a pas de section en cours à ce
niveau. {{ section.settings.titre }} dans la coquille rend du vide.
Un {% schema %} dans layout/theme.liquid casse la page : schema
n’est pas un tag reconnu, et le fichier est rendu tel quel, sans que le bloc
ne soit retiré comme il l’est pour les sections. L’erreur remontée est
tag "schema" not found. La coquille n’a pas de réglages propres — ceux dont
elle a besoin sont les réglages globaux.
#Une coquille minimale qui fonctionne
Pour démarrer, ou pour isoler un problème :
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ page.titre }} — {{ site.nom }}</title>
{{ 'theme.css' | asset_url | stylesheet_tag }}
</head>
<body>
{{ content_for_layout }}
</body>
</html>#Les erreurs qui se voient le moins
content_for_layout oubliéLa page s’affiche, l’en-tête et le pied aussi, et le contenu n’est nulle part. Aucune erreur.
asset_url avec le chemin completLe site rend en HTML nu. La page fonctionne, elle est juste sans style. À
vérifier dans l’onglet réseau : un 404 sur /theme-assets/…/assets/theme.css.
Une variable mal orthographiée{{ settings.acent }} rend une chaîne vide. Le moteur tourne avec
strictVariables désactivé, exprès : une faute de frappe dans un coin de
pied de page ne doit pas éteindre la page entière. Traître en développement,
salutaire en production.
Une section de coquille absente du thème{% section 'annonce' %} sans sections/annonce.liquid rend un commentaire
HTML. Invisible à l’écran, lisible dans le code source de la page.

