fix(marketing): pricing — honest ROI payback + capped sliders + URL hygiene

- ROI payback now returns raw months; template branches to 'moins d'un mois'
  for sub-month paybacks and 'Payable dès la première année' when savings≤0
  (was rounding up to 'Payback : 1 mois' for ~95% of slider combos)
- Cap sliders: users 1..25 (was 50), hours 0.5..4 (was 8) to keep displayed
  savings in a defensible band (~8.8 M$/yr max instead of 35 M$)
- pricing_card href uses cta_url.rstrip('/') to avoid double-slash if caller
  passes a trailing slash (preempts A-2.8 / B-2.7 regression)
- aria-live polite + aria-atomic on the savings paragraph so screen readers
  announce slider updates
- Cleaner JS module pattern: single window.roiCalculator = function() {...}
- Tests updated for payback ternary; new tests for slider caps, aria-live,
  and double-slash guard

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Allison
2026-04-27 19:05:36 -04:00
parent 0ae4053faa
commit 7d67b64ddc
4 changed files with 48 additions and 11 deletions

View File

@@ -35,7 +35,7 @@
{% endfor %}
</ul>
{% from 'macros/button.html' import button %}
{{ button('Réserver ' + name, href=cta_url + '/' + slug, variant='primary' if recommended else 'secondary', size='lg') }}
{{ button('Réserver ' + name, href=cta_url.rstrip('/') + '/' + slug, variant='primary' if recommended else 'secondary', size='lg') }}
</div>
</div>
{%- endmacro -%}

View File

@@ -256,12 +256,12 @@
<div class="grid sm:grid-cols-3 gap-4 mb-6">
<label class="flex flex-col">
<span class="text-xs font-semibold mb-1 text-brand-navy">Utilisateurs</span>
<input type="range" x-model.number="users" min="1" max="50" step="1" class="accent-brand-b1" aria-label="Nombre d'utilisateurs">
<input type="range" x-model.number="users" min="1" max="25" step="1" class="accent-brand-b1" aria-label="Nombre d'utilisateurs">
<span class="text-sm text-brand-navy/70" x-text="users + ' utilisateur' + (users > 1 ? 's' : '')"></span>
</label>
<label class="flex flex-col">
<span class="text-xs font-semibold mb-1 text-brand-navy">Heures audio / jour</span>
<input type="range" x-model.number="hours" min="0.5" max="8" step="0.5" class="accent-brand-b1" aria-label="Heures d'audio par jour">
<input type="range" x-model.number="hours" min="0.5" max="4" step="0.5" class="accent-brand-b1" aria-label="Heures d'audio par jour">
<span class="text-sm text-brand-navy/70" x-text="hours + ' h/jour'"></span>
</label>
<label class="flex flex-col">
@@ -272,8 +272,10 @@
</div>
<div class="text-center pt-6 border-t border-brand-border">
<p class="text-sm text-brand-navy/70 mb-2">Économies estimées par an</p>
<p class="text-5xl font-black grad-text" x-text="savings.toLocaleString('fr-CA') + ' $'"></p>
<p class="text-sm text-brand-navy/70 mt-2" x-text="'Payback : ' + payback + ' mois'"></p>
<p class="text-5xl font-black grad-text" aria-live="polite" aria-atomic="true"
x-text="savings.toLocaleString('fr-CA') + ' $'"></p>
<p class="text-sm text-brand-navy/70 mt-2"
x-text="payback === null ? 'Payable dès la première année' : (payback < 1 ? 'Payback : moins d\'un mois' : 'Payback : ' + Math.round(payback) + ' mois')"></p>
</div>
<p class="text-xs text-brand-navy/70 mt-6 text-center">
Hypothèses&nbsp;: 80&nbsp;% du temps de transcription manuelle économisé, 220 jours ouvrables/an, comparé à DictIA 16 (5&nbsp;750&nbsp;$ + 201&nbsp;$/mois). Estimation à titre indicatif.