⏳ Comptes à reboursGratuit · 0 NahnahPar NAHNAH AI
Lancement de produit
Lancement d’un produit : image, titre et compte à rebours jusqu’à la vraie date de sortie, puis le bouton « Découvrir » apparaît automatiquement à l’heure dite.
Aperçu en direct
Données de démonstration (produits, menus, textes d’exemple) : dans ta boutique, ce sont les tiennes.
Photos : Unsplash / Pexels (exemples de l’aperçu, jamais inclus dans le code).
Code à copier
Fichier complet sections/nahnah-rebours-lancement-produit.liquid (Liquid, CSS et JS inclus).
{%- comment -%} NAHNAH AI · Lancement de produit (bibliothèque Sections & effets) {%- endcomment -%}
{%- if section.settings.date_fin != blank -%}
<nn-rebours-lancement-produit class="nn-rebours-lancement-produit" style="--nn-mh: {{ section.settings.marge_haut }}px; --nn-mb: {{ section.settings.marge_bas }}px; --nn-fond: {{ section.settings.couleur_fond }}; --nn-texte: {{ section.settings.couleur_texte }}; --nn-accent: {{ section.settings.couleur_accent }}; --nn-bouton: {{ section.settings.couleur_bouton }}; --nn-texte-bouton: {{ section.settings.couleur_texte_bouton }};" data-mode="date" data-zone="{{ section.settings.fuseau | escape }}" data-libelles="{{ section.settings.libelles | escape }}" data-fin="{{ section.settings.date_fin | escape }}" data-a-la-fin="{{ section.settings.a_la_fin }}"{% if request.design_mode %} data-editeur{% endif %}>
{%- assign nn_p = section.settings.produit -%}
{%- assign nn_lien = section.settings.lien -%}
{%- if nn_lien == blank and nn_p != blank -%}{%- assign nn_lien = nn_p.url -%}{%- endif -%}
<div class="nn-rebours-lancement-produit__grille">
{%- if nn_p.featured_image != blank -%}
<div class="nn-rebours-lancement-produit__media">{{ nn_p.featured_image | image_url: width: 900 | image_tag: loading: 'lazy', class: 'nn-rebours-lancement-produit__image', sizes: '(min-width: 750px) 40vw, 100vw', widths: '360, 540, 720, 900' }}</div>
{%- endif -%}
<div class="nn-rebours-lancement-produit__texte">
{%- if section.settings.surtitre != blank -%}<p class="nn-rebours-lancement-produit__surtitre">{{ section.settings.surtitre | escape }}</p>{%- endif -%}
<h2 class="nn-rebours-lancement-produit__titre">{%- if section.settings.titre != blank -%}{{ section.settings.titre }}{%- else -%}{{ nn_p.title | escape }}{%- endif -%}</h2>
<div data-pendant>
{%- assign nn_l = section.settings.libelles | split: ',' -%}
<div class="nn-rebours-lancement-produit__unites" role="timer" aria-live="off" aria-atomic="true">
<div class="nn-rebours-lancement-produit__bloc" data-bloc-jours>
<span class="nn-rebours-lancement-produit__nombre" data-unite="j">--</span>
<span class="nn-rebours-lancement-produit__libelle">{{ nn_l[0] | strip | escape }}</span>
</div>
<div class="nn-rebours-lancement-produit__bloc">
<span class="nn-rebours-lancement-produit__nombre" data-unite="h">--</span>
<span class="nn-rebours-lancement-produit__libelle">{{ nn_l[1] | strip | escape }}</span>
</div>
<div class="nn-rebours-lancement-produit__bloc">
<span class="nn-rebours-lancement-produit__nombre" data-unite="m">--</span>
<span class="nn-rebours-lancement-produit__libelle">{{ nn_l[2] | strip | escape }}</span>
</div>
<div class="nn-rebours-lancement-produit__bloc">
<span class="nn-rebours-lancement-produit__nombre" data-unite="s">--</span>
<span class="nn-rebours-lancement-produit__libelle">{{ nn_l[3] | strip | escape }}</span>
</div>
</div>
<p class="nn-rebours-lancement-produit__echeance">{{ section.settings.texte_echeance | escape }} <span data-echeance></span></p>
</div>
<div class="nn-rebours-lancement-produit__apres" data-apres hidden>
<p class="nn-rebours-lancement-produit__fin">{{ section.settings.message_fin }}</p>
{%- if nn_lien != blank and section.settings.texte_bouton != blank -%}
<a class="nn-rebours-lancement-produit__bouton" href="{{ nn_lien }}">{{ section.settings.texte_bouton | escape }}</a>
{%- endif -%}
</div>
</div>
</div>
<span class="nn-rebours-lancement-produit__lecteur">Fin : <span data-echeance></span></span>
<span class="nn-rebours-lancement-produit__lecteur" role="status" data-annonce data-texte-fin="{{ section.settings.message_fin | strip_html | escape }}"></span>
<p class="nn-rebours-lancement-produit__alerte" data-alerte hidden>Date illisible : écris-la par exemple 24/12/2026 18:00 (message visible dans l’éditeur seulement).</p>
<p class="nn-rebours-lancement-produit__alerte" data-note-masque hidden>Date passée : la section est masquée pour les visiteurs (ce message n’apparaît que dans l’éditeur).</p>
</nn-rebours-lancement-produit>
{%- elsif request.design_mode -%}
<p class="nn-rebours-lancement-produit__editeur">Indique la vraie date de fin de ton offre pour afficher le compte à rebours.</p>
{%- endif -%}
{% schema %}
{
"name": "Rebours lancement",
"tag": "section",
"class": "nn-section-rebours-lancement-produit",
"settings": [
{
"type": "product",
"id": "produit",
"label": "Produit lancé (facultatif)"
},
{
"type": "text",
"id": "surtitre",
"label": "Surtitre",
"default": "Bientôt disponible"
},
{
"type": "inline_richtext",
"id": "titre",
"label": "Titre",
"default": "Notre nouveauté arrive",
"info": "Vide : nom du produit choisi."
},
{
"type": "text",
"id": "texte_echeance",
"label": "Texte avant la date de sortie",
"default": "Sortie le"
},
{
"type": "text",
"id": "texte_bouton",
"label": "Texte du bouton (après la sortie)",
"default": "Découvrir"
},
{
"type": "url",
"id": "lien",
"label": "Lien du bouton",
"info": "Vide : page du produit choisi."
},
{
"type": "header",
"content": "Fin de l’offre"
},
{
"type": "text",
"id": "date_fin",
"label": "Date et heure de sortie",
"placeholder": "24/12/2026 18:00",
"info": "Vraie date de fin de ton offre, à l’heure du fuseau choisi : 24/12/2026 18:00 ou 2026-12-24 18:00 (sans heure : 23:59). Le compte à rebours ne repart jamais à zéro."
},
{
"type": "select",
"id": "fuseau",
"label": "Fuseau horaire des dates",
"options": [
{
"value": "Europe/Paris",
"label": "France, Belgique, Suisse, Luxembourg (Paris)"
},
{
"value": "Europe/London",
"label": "Royaume-Uni, Irlande (Londres)"
},
{
"value": "Europe/Lisbon",
"label": "Portugal (Lisbonne)"
},
{
"value": "America/Toronto",
"label": "Québec, Ontario (Montréal)"
},
{
"value": "America/New_York",
"label": "États-Unis, côte Est (New York)"
},
{
"value": "America/Martinique",
"label": "Martinique"
},
{
"value": "America/Guadeloupe",
"label": "Guadeloupe"
},
{
"value": "America/Cayenne",
"label": "Guyane"
},
{
"value": "Indian/Reunion",
"label": "La Réunion"
},
{
"value": "Indian/Mayotte",
"label": "Mayotte"
},
{
"value": "Pacific/Noumea",
"label": "Nouvelle-Calédonie"
},
{
"value": "Pacific/Tahiti",
"label": "Polynésie française (Tahiti)"
},
{
"value": "Africa/Casablanca",
"label": "Maroc (Casablanca)"
},
{
"value": "Africa/Algiers",
"label": "Algérie (Alger)"
},
{
"value": "Africa/Tunis",
"label": "Tunisie (Tunis)"
},
{
"value": "Africa/Dakar",
"label": "Sénégal (Dakar)"
},
{
"value": "Africa/Abidjan",
"label": "Côte d’Ivoire (Abidjan)"
},
{
"value": "Africa/Douala",
"label": "Cameroun (Douala)"
},
{
"value": "Africa/Kinshasa",
"label": "RD Congo (Kinshasa)"
},
{
"value": "UTC",
"label": "Temps universel (UTC)"
}
],
"default": "Europe/Paris"
},
{
"type": "select",
"id": "a_la_fin",
"label": "Une fois la date passée",
"options": [
{
"value": "message",
"label": "Afficher le message de fin"
},
{
"value": "masquer",
"label": "Masquer la section"
}
],
"default": "message"
},
{
"type": "inline_richtext",
"id": "message_fin",
"label": "Message de fin",
"default": "C’est lancé : le produit est disponible !"
},
{
"type": "header",
"content": "Affichage"
},
{
"type": "text",
"id": "libelles",
"label": "Libellés des unités",
"default": "jours, heures, minutes, secondes",
"info": "Jours, heures, minutes, secondes : séparés par des virgules, dans cet ordre."
},
{
"type": "header",
"content": "Couleurs"
},
{
"type": "color",
"id": "couleur_fond",
"label": "Fond",
"default": "#0B1F3A"
},
{
"type": "color",
"id": "couleur_texte",
"label": "Texte",
"default": "#FFFFFF"
},
{
"type": "color",
"id": "couleur_accent",
"label": "Accent",
"default": "#2EE6A8"
},
{
"type": "color",
"id": "couleur_bouton",
"label": "Bouton",
"default": "#2EE6A8"
},
{
"type": "color",
"id": "couleur_texte_bouton",
"label": "Texte du bouton",
"default": "#0B1F3A"
},
{
"type": "header",
"content": "Espacement"
},
{
"type": "range",
"id": "marge_haut",
"label": "Marge haute",
"min": 0,
"max": 120,
"step": 4,
"unit": "px",
"default": 48
},
{
"type": "range",
"id": "marge_bas",
"label": "Marge basse",
"min": 0,
"max": 120,
"step": 4,
"unit": "px",
"default": 48
}
],
"presets": [
{
"name": "Rebours lancement"
}
]
}
{% endschema %}
{% stylesheet %}
.nn-rebours-lancement-produit {
display: block;
padding: var(--nn-mh, 0px) 16px var(--nn-mb, 0px);
background: var(--nn-fond);
color: var(--nn-texte);
}
.nn-rebours-lancement-produit[hidden], .nn-rebours-lancement-produit [hidden] { display: none !important; }
.nn-rebours-lancement-produit *, .nn-rebours-lancement-produit *::before, .nn-rebours-lancement-produit *::after { box-sizing: border-box; }
.nn-rebours-lancement-produit :focus-visible { outline: 3px solid var(--nn-accent); outline-offset: 3px; }
.nn-rebours-lancement-produit__editeur { margin: 0; padding: 16px; font-size: 14px; text-align: center; border: 1px dashed currentColor; opacity: 0.7; }
.nn-rebours-lancement-produit { position: relative; }
.nn-rebours-lancement-produit__lecteur { position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
.nn-rebours-lancement-produit__alerte { max-width: 640px; margin: 12px auto 0; padding: 8px 12px; font-size: 13px; line-height: 1.4; text-align: center; border: 1px dashed currentColor; border-radius: 8px; opacity: 0.85; }
.nn-rebours-lancement-produit__fin { margin: 0; overflow-wrap: anywhere; }
.nn-rebours-lancement-produit [data-unite], .nn-rebours-lancement-produit [data-texte] { font-variant-numeric: tabular-nums; }
.nn-rebours-lancement-produit__grille { display: grid; gap: 24px; align-items: center; max-width: 1100px; margin: 0 auto; }
@media (min-width: 750px) { .nn-rebours-lancement-produit__grille { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); gap: 48px; } }
.nn-rebours-lancement-produit__media { overflow: hidden; border-radius: 18px; background: color-mix(in srgb, var(--nn-texte) 8%, transparent); }
.nn-rebours-lancement-produit__image { display: block; width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; }
.nn-rebours-lancement-produit__texte { min-width: 0; }
.nn-rebours-lancement-produit__surtitre { margin: 0 0 8px; font-size: 13px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--nn-accent); }
.nn-rebours-lancement-produit__titre { margin: 0 0 20px; font-size: clamp(26px, 5vw, 44px); line-height: 1.1; overflow-wrap: anywhere; }
.nn-rebours-lancement-produit__unites { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(6px, 2vw, 14px); }
.nn-rebours-lancement-produit__bloc { display: flex; flex-direction: column; align-items: center; justify-content: center; min-width: clamp(64px, 18vw, 112px); padding: clamp(10px, 2.5vw, 16px) 6px; border-radius: 14px; background: color-mix(in srgb, var(--nn-texte) 8%, transparent); border: 1px solid color-mix(in srgb, var(--nn-accent) 35%, transparent); }
.nn-rebours-lancement-produit__nombre { font-size: clamp(28px, 8vw, 52px); font-weight: 800; line-height: 1; }
.nn-rebours-lancement-produit__libelle { margin-top: 6px; font-size: clamp(10px, 2.8vw, 13px); letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.8; }
.nn-rebours-lancement-produit__unites { justify-content: flex-start; }
.nn-rebours-lancement-produit__nombre { color: var(--nn-accent); }
.nn-rebours-lancement-produit__echeance { margin: 14px 0 0; font-size: 14px; opacity: 0.8; }
.nn-rebours-lancement-produit__fin { margin-bottom: 16px; font-size: 18px; font-weight: 600; }
.nn-rebours-lancement-produit__bouton {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 44px;
max-width: 100%;
padding: 12px 24px;
border: 0;
border-radius: 999px;
background: var(--nn-bouton);
color: var(--nn-texte-bouton);
font: inherit;
font-weight: 700;
line-height: 1.2;
text-align: center;
text-decoration: none;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
@media (hover: hover) { .nn-rebours-lancement-produit__bouton:hover { transform: translateY(-2px); box-shadow: 0 8px 20px color-mix(in srgb, var(--nn-bouton) 35%, transparent); } }
.nn-rebours-lancement-produit__bouton:active { transform: scale(0.97); }
@media (prefers-reduced-motion: reduce) {
.nn-rebours-lancement-produit, .nn-rebours-lancement-produit *, .nn-rebours-lancement-produit *::before, .nn-rebours-lancement-produit *::after {
animation: none !important;
transition: none !important;
}
}
{% endstylesheet %}
{% javascript %}
if (!customElements.get("nn-rebours-lancement-produit")) {
customElements.define("nn-rebours-lancement-produit", class extends HTMLElement {
connectedCallback() {
if (this.dataset.nnPret) return;
this.dataset.nnPret = "1";
var soi = this;
this.editeur = this.hasAttribute("data-editeur");
this.zone = this.dataset.zone || "UTC";
this.unites = {};
this.dernier = {};
["j", "h", "m", "s"].forEach(function (u) { soi.unites[u] = Array.prototype.slice.call(soi.querySelectorAll('[data-unite="' + u + '"]')); });
this.libelles = String(this.dataset.libelles || "j,h,min,s").split(",").map(function (x) { return x.trim(); });
this.debut = this.lireDate(this.dataset.debut);
this.fin = this.calculerFin();
if (this.querySelector("[data-progres]") && !Number.isFinite(this.debut)) this.fin = NaN;
this.ecrireEcheance();
this.brancherCopie();
this.tic();
}
disconnectedCallback() {
clearTimeout(this.minuterie);
delete this.dataset.nnPret;
delete this.dataset.etat;
}
// Écart (ms) entre l'heure locale du fuseau et UTC à l'instant t (heure d'été comprise)
decalage(t) {
try {
if (!this.formateur) this.formateur = new Intl.DateTimeFormat("en-US", { timeZone: this.zone, hourCycle: "h23", year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric" });
var p = {};
this.formateur.formatToParts(new Date(t)).forEach(function (x) { p[x.type] = x.value; });
return Date.UTC(Number(p.year), Number(p.month) - 1, Number(p.day), Number(p.hour) % 24, Number(p.minute), Number(p.second)) - Math.floor(t / 1000) * 1000;
} catch (e) { return NaN; }
}
// Instant UTC d'une heure « murale » du fuseau choisi
instant(a, mo, j, h, mi, s) {
var mur = Date.UTC(a, mo - 1, j, h, mi, s);
return mur - this.decalage(mur - this.decalage(mur));
}
// Date saisie par le marchand : 24/12/2026 18:00, 2026-12-24 18:00 (fuseau choisi) ou ISO avec décalage
lireDate(brut) {
var texte = String(brut || "").trim();
if (!texte) return NaN;
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})$/i.test(texte)) return Date.parse(texte);
var m = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[\sT]+(\d{1,2})(?:[:hH](\d{2})?)?)?$/.exec(texte);
var p = m ? [m[1], m[2], m[3], m[4], m[5]] : null;
if (!p) {
m = /^(\d{1,2})[\/.-](\d{1,2})[\/.-](\d{4})(?:[\s,]+(?:[aA]\s+)?(\d{1,2})(?:[:hH](\d{2})?)?)?$/.exec(texte);
if (m) p = [m[3], m[2], m[1], m[4], m[5]];
}
if (!p) return NaN;
var sansHeure = p[3] === undefined;
var n = p.map(function (x) { return Number(x || 0); });
if (n[1] < 1 || n[1] > 12 || n[2] < 1 || n[2] > 31 || n[3] > 23 || n[4] > 59) return NaN;
return sansHeure ? this.instant(n[0], n[1], n[2], 23, 59, 59) : this.instant(n[0], n[1], n[2], n[3], n[4], 0);
}
lireJour(x) {
var m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(x);
var a, mo, j;
if (m) { a = m[1]; mo = m[2]; j = m[3]; }
else {
m = /^(\d{1,2})[\/.](\d{1,2})[\/.](\d{4})$/.exec(x);
if (!m) return "";
a = m[3]; mo = m[2]; j = m[1];
}
return a + "-" + String(mo).padStart(2, "0") + "-" + String(j).padStart(2, "0");
}
calculerFin() {
var mode = this.dataset.mode || "date";
if (mode === "quotidien") return this.prochaineLimite();
var fin = this.lireDate(this.dataset.fin);
if (mode !== "visiteur") return fin;
// Offre par visiteur : début mémorisé UNE fois, jamais remis à zéro ; sans mémoire fiable, rien n'est affiché
var heures = Number(this.dataset.duree);
if (!(heures > 0)) return NaN;
var debut = Date.now();
if (!this.editeur) {
var cle = "nn-rebours-visiteur:" + (this.dataset.cle || "offre");
try {
var lu = Number(localStorage.getItem(cle));
if (lu > 0 && lu <= Date.now()) debut = lu;
else localStorage.setItem(cle, String(debut));
if (Number(localStorage.getItem(cle)) !== debut) return NaN;
} catch (e) { return NaN; }
}
this.debut = debut;
var finVisiteur = debut + heures * 3600000;
return Number.isFinite(fin) ? Math.min(fin, finVisiteur) : finVisiteur;
}
// Prochaine heure limite quotidienne réelle (jours d'expédition et jours fermés du marchand)
prochaineLimite() {
var hm = /^(\d{1,2})(?:[:hH](\d{2})?)?$/.exec(String(this.dataset.heure || "").trim());
if (!hm || Number(hm[1]) > 23 || Number(hm[2] || 0) > 59) return NaN;
var maintenant = Date.now();
var dec = this.decalage(maintenant);
if (!Number.isFinite(dec)) return NaN;
var mur = new Date(maintenant + dec);
var jours = this.dataset.jours || "1-5";
var soi = this;
var fermes = String(this.dataset.fermes || "").split(/[\s,;]+/).map(function (x) { return soi.lireJour(x); });
for (var k = 0; k < 21; k++) {
var jour = new Date(Date.UTC(mur.getUTCFullYear(), mur.getUTCMonth(), mur.getUTCDate() + k));
var semaine = jour.getUTCDay();
if (jours === "1-5" && (semaine === 0 || semaine === 6)) continue;
if (jours === "1-6" && semaine === 0) continue;
if (fermes.indexOf(jour.toISOString().slice(0, 10)) >= 0) continue;
var limite = this.instant(jour.getUTCFullYear(), jour.getUTCMonth() + 1, jour.getUTCDate(), Number(hm[1]), Number(hm[2] || 0), 0);
if (limite > maintenant) {
this.ecartJours = Math.round((Date.UTC(jour.getUTCFullYear(), jour.getUTCMonth(), jour.getUTCDate()) - Date.UTC(mur.getUTCFullYear(), mur.getUTCMonth(), mur.getUTCDate())) / 86400000);
this.jourLimite = jour;
return limite;
}
}
return NaN;
}
ecrireEcheance() {
if (!Number.isFinite(this.fin)) return;
var langue = document.documentElement.lang || "fr";
var texte = "";
try {
var o = { weekday: "long", day: "numeric", month: "long", hour: "2-digit", minute: "2-digit", timeZone: this.zone };
if (new Date(this.fin).getFullYear() !== new Date().getFullYear()) o.year = "numeric";
texte = new Intl.DateTimeFormat(langue, o).format(new Date(this.fin));
} catch (e) { texte = new Date(this.fin).toLocaleString(); }
this.querySelectorAll("[data-echeance]").forEach(function (n) { n.textContent = texte; });
if (!this.jourLimite) return;
var nom = this.ecartJours === 0 ? this.dataset.aujourdhui || "" : this.ecartJours === 1 ? this.dataset.demain || "" : "";
if (!nom) {
try { nom = new Intl.DateTimeFormat(langue, { weekday: "long", timeZone: "UTC" }).format(this.jourLimite); } catch (e) { nom = ""; }
}
this.querySelectorAll("[data-jour]").forEach(function (n) { n.textContent = nom; });
}
brancherCopie() {
var soi = this;
var bouton = this.querySelector("[data-copier]");
var code = this.querySelector("[data-code]");
var statut = this.querySelector("[data-statut]");
if (!bouton || !code || bouton.dataset.nnBranche) return;
bouton.dataset.nnBranche = "1";
var dire = function (texte) {
if (statut) statut.textContent = texte;
soi.classList.add("est-copie");
clearTimeout(soi.minuterieCopie);
soi.minuterieCopie = setTimeout(function () { soi.classList.remove("est-copie"); if (statut) statut.textContent = ""; }, 2500);
};
var selectionner = function () {
try {
var plage = document.createRange();
plage.selectNodeContents(code);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(plage);
dire(bouton.dataset.selection || "Code sélectionné");
} catch (e) { dire(""); }
};
bouton.addEventListener("click", function () {
var texte = code.textContent.trim();
try {
navigator.clipboard.writeText(texte).then(function () { dire(bouton.dataset.copie || "Copié !"); }, selectionner);
} catch (e) { selectionner(); }
});
}
tic() {
clearTimeout(this.minuterie);
var soi = this;
var maintenant = Date.now();
var reste = this.fin - maintenant;
if (!Number.isFinite(reste)) return this.invalide();
if (reste <= 0) {
if (this.dataset.mode === "quotidien") {
this.fin = this.prochaineLimite();
this.ecrireEcheance();
if (!(this.fin > maintenant)) return this.invalide();
reste = this.fin - maintenant;
} else {
var prolongation = (Number(this.dataset.prolongation) || 0) * 60000;
if (maintenant < this.fin + prolongation) {
this.afficher({ j: 0, h: 0, m: 0, s: 0 });
this.etat("direct");
this.minuterie = setTimeout(function () { soi.tic(); }, Math.min(60000, this.fin + prolongation - maintenant + 50));
return;
}
return this.terminer();
}
}
var s = Math.floor(reste / 1000);
this.afficher({ j: Math.floor(s / 86400), h: Math.floor((s % 86400) / 3600), m: Math.floor((s % 3600) / 60), s: s % 60 });
this.etat("pendant");
this.actif = true;
this.minuterie = setTimeout(function () { soi.tic(); }, 1000 - (Date.now() % 1000) + 20);
}
afficher(v) {
var soi = this;
var flip = this.hasAttribute("data-flip");
var sansJours = v.j === 0;
this.querySelectorAll("[data-bloc-jours]").forEach(function (n) { n.hidden = sansJours; });
["j", "h", "m", "s"].forEach(function (u) {
var texte = String(v[u]).padStart(2, "0");
if (soi.dernier[u] === texte) return;
var premier = soi.dernier[u] === undefined;
soi.dernier[u] = texte;
soi.unites[u].forEach(function (n) {
n.textContent = texte;
if (flip && !premier) {
var t = n.closest("[data-tuile]") || n;
t.classList.remove("est-change");
void t.offsetWidth;
t.classList.add("est-change");
}
});
});
if (!this.joursMax) this.joursMax = Math.max(1, Number(this.dataset.joursMax) || v.j || 1);
var max = { j: this.joursMax, h: 24, m: 60, s: 60 };
this.querySelectorAll("[data-cercle]").forEach(function (n) {
n.style.setProperty("--nn-p", Math.min(1, v[n.getAttribute("data-cercle")] / max[n.getAttribute("data-cercle")]).toFixed(4));
});
if (Number.isFinite(this.debut) && this.fin > this.debut) {
var p = Math.min(1, Math.max(0, (Date.now() - this.debut) / (this.fin - this.debut)));
this.querySelectorAll("[data-progres]").forEach(function (n) { n.style.setProperty("--nn-p", p.toFixed(4)); });
this.querySelectorAll("[data-pourcent]").forEach(function (n) { n.textContent = Math.floor(p * 100) + " %"; });
}
var textes = this.querySelectorAll("[data-texte]");
if (!textes.length) return;
var l = this.libelles;
var morceaux = [];
if (this.dataset.format === "horloge") {
morceaux.push(String(v.h + v.j * 24).padStart(2, "0") + ":" + String(v.m).padStart(2, "0") + ":" + String(v.s).padStart(2, "0"));
} else {
if (v.j) morceaux.push(v.j + " " + (l[0] || "j"));
if (v.j || v.h) morceaux.push(v.h + " " + (l[1] || "h"));
morceaux.push(v.m + " " + (l[2] || "min"));
if (!this.hasAttribute("data-sans-secondes")) morceaux.push(v.s + " " + (l[3] || "s"));
}
var phrase = morceaux.join(" ");
textes.forEach(function (n) { if (n.textContent !== phrase) n.textContent = phrase; });
}
etat(e) {
if (this.dataset.etat === e) return;
var avant = this.dataset.etat;
this.dataset.etat = e;
this.querySelectorAll("[data-pendant]").forEach(function (n) { n.hidden = e !== "pendant"; });
this.querySelectorAll("[data-direct]").forEach(function (n) { n.hidden = e !== "direct"; });
this.querySelectorAll("[data-apres]").forEach(function (n) { n.hidden = e !== "apres"; });
// Une seule annonce polie, et seulement si l'offre se termine pendant la visite
var annonce = this.querySelector("[data-annonce]");
if (annonce && avant && this.actif && (e === "apres" || e === "direct")) annonce.textContent = annonce.getAttribute(e === "direct" ? "data-texte-direct" : "data-texte-fin") || "";
}
montrer(selecteur) {
this.querySelectorAll(selecteur).forEach(function (n) { n.hidden = false; });
}
terminer() {
this.afficher({ j: 0, h: 0, m: 0, s: 0 });
this.etat("apres");
if (this.dataset.aLaFin !== "masquer") return;
if (this.editeur) this.montrer("[data-note-masque]");
else this.hidden = true;
}
invalide() {
this.etat("invalide");
if (this.editeur) this.montrer("[data-alerte]");
else this.hidden = true;
}
});
}
{% endjavascript %}
Licence NAHNAH AI : utilisation libre dans tes boutiques (y compris commerciales), modification permise, revente de l’élément seul interdite.
Prompt à copier
À coller dans Claude ou ChatGPT avec le fichier téléchargé (ou le code copié) : l’assistant t’aide à l’installer et à l’adapter à ta marque.
Tu es un expert des thèmes Shopify Online Store 2.0. Aide-moi à installer puis à adapter à ma marque cet élément gratuit de la bibliothèque NAHNAH AI.
Élément : « Lancement de produit » (Comptes à rebours)
Description : Lancement d’un produit : image, titre et compte à rebours jusqu’à la vraie date de sortie, puis le bouton « Découvrir » apparaît automatiquement à l’heure dite.
Page de l’élément (aperçu en direct) : https://ai.nahnah.shop/bibliotheque-shopify/rebours-lancement-produit
Fichier : nahnah-rebours-lancement-produit.liquid (bouton « Télécharger » de la page) ou le code copié avec le bouton « Copier » : je te le joins ou je le colle après ce message.
Emplacement dans le thème : sections/nahnah-rebours-lancement-produit.liquid (Liquid, puis {% stylesheet %} et {% javascript %}, dans ce seul fichier).
Étapes d’installation :
1. Dans Shopify : Boutique en ligne → Thèmes → bouton « … » du thème → Modifier le code.
2. Dossier « sections » → « Ajouter une section » (ou « Nouveau fichier ») → nomme-la « nahnah-rebours-lancement-produit ».
3. Efface le contenu proposé, colle le code de l’onglet Liquid (fichier complet), puis Enregistrer.
4. Ouvre Personnaliser → Ajouter une section → choisis-la, règle les textes et les couleurs, puis Enregistrer.
Ce que j’attends de toi :
- Guide-moi pas à pas dans Shopify : Boutique en ligne → Thèmes → « … » → Modifier le code → dossier sections → Ajouter une section, puis Personnaliser → Ajouter une section.
- Propose-moi d’adapter les couleurs, les textes et les liens à ma marque : demande d’abord mes couleurs (codes hexadécimaux), mon ton et mes liens.
- Garde le bloc {% schema %} et tous ses réglages (identifiants, types, presets) : je dois pouvoir tout régler ensuite dans l’éditeur de thème.
- Rends-moi le fichier complet modifié, prêt à coller, sans faux avis, fausse urgence ni chiffre inventé.
Si tu as le connecteur NAHNAH AI (MCP), récupère directement le code exact avec l’outil obtenir_code_element (id : rebours-lancement-produit).Installer dans ma boutique
En un clic, la section est ajoutée à un thème de ta boutique Shopify connectée (un thème non publié par défaut).
Chargement de tes boutiques…
Installer dans Shopify
- Dans Shopify : Boutique en ligne → Thèmes → bouton « … » du thème → Modifier le code.
- Dossier « sections » → « Ajouter une section » (ou « Nouveau fichier ») → nomme-la « nahnah-rebours-lancement-produit ».
- Efface le contenu proposé, colle le code de l’onglet Liquid (fichier complet), puis Enregistrer.
- Ouvre Personnaliser → Ajouter une section → choisis-la, règle les textes et les couleurs, puis Enregistrer.
Installer via le MCP
Depuis Claude, Claude Code ou ChatGPT connecté à NAHNAH AI, demande : « Donne-moi le code de l’élément rebours-lancement-produit de la bibliothèque NAHNAH AI et les étapes pour l’installer ». L’assistant récupère le code et les étapes (outil obtenir_code_element), gratuitement.
Boutique connectée à NAHNAH AI : l’assistant peut aussi l’installer directement (outil installer_element).
Connecter le MCP