refactor(pricing): 3 Cloud en rangée + DictIA LOCAL bloc dédié 'Vous en êtes propriétaire'

Restructure _pricing_tiers.html : les 3 forfaits Cloud (Basic 189$/Essentiel 349$/Pro 549$ recommandé) sont maintenant en grid responsive 1/2/3 cols, et DictIA LOCAL est sorti de la grille principale pour devenir un bloc large dédié 'propriété' avec :

- badge 'Au Québec · par InnovA AI' (SVG map-pin, sans emoji 🇨🇦)
- H3 'Vous en êtes propriétaire.' avec grad-text
- 5 bullets checkmark (PC+GPU RTX, 100 % local, assemblé QC, installation incluse, achat direct < 34 700 $)
- CTA 'Voir les serveurs disponibles' → /contact?plan=dictia-local
- mockup serveur à droite (SVG rack + 6 specs : Interface web, PC gaming, RTX 5070 Ti 16 Go, WhisperX+Mistral, DictIA pré-installé, Votre propriété)
- pricing tagline visible '5 998 $ An 1 · 500 $/an dès An 2'
- decorative orbs background (b1 + b3) pour distinguer du grid Cloud

Aussi mis à jour /tarifs (H1 'Trois forfaits Cloud + DictIA LOCAL' au lieu de 'Quatre forfaits') et tests pour refléter le nouveau slug /contact?plan=dictia-local (au lieu du /checkout/dictia-local d'avant). Conserve V3 radii (rounded-none/rounded/rounded-full), palette brand (b1/b2/b3/navy), OQLF NBSP, ARIA WCAG, zéro emoji.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Allison
2026-04-28 21:28:46 -04:00
parent 1c4cafaf69
commit e06cba2123
5 changed files with 171 additions and 44 deletions

View File

@@ -399,6 +399,9 @@
.-top-20 { .-top-20 {
top: calc(var(--spacing) * -20); top: calc(var(--spacing) * -20);
} }
.-top-32 {
top: calc(var(--spacing) * -32);
}
.top-0 { .top-0 {
top: calc(var(--spacing) * 0); top: calc(var(--spacing) * 0);
} }
@@ -453,6 +456,9 @@
.-right-24 { .-right-24 {
right: calc(var(--spacing) * -24); right: calc(var(--spacing) * -24);
} }
.-right-32 {
right: calc(var(--spacing) * -32);
}
.right-0 { .right-0 {
right: calc(var(--spacing) * 0); right: calc(var(--spacing) * 0);
} }
@@ -489,6 +495,9 @@
.-bottom-24 { .-bottom-24 {
bottom: calc(var(--spacing) * -24); bottom: calc(var(--spacing) * -24);
} }
.-bottom-32 {
bottom: calc(var(--spacing) * -32);
}
.bottom-0 { .bottom-0 {
bottom: calc(var(--spacing) * 0); bottom: calc(var(--spacing) * 0);
} }
@@ -507,6 +516,9 @@
.-left-20 { .-left-20 {
left: calc(var(--spacing) * -20); left: calc(var(--spacing) * -20);
} }
.-left-32 {
left: calc(var(--spacing) * -32);
}
.left-0 { .left-0 {
left: calc(var(--spacing) * 0); left: calc(var(--spacing) * 0);
} }
@@ -1411,6 +1423,9 @@
.gap-8 { .gap-8 {
gap: calc(var(--spacing) * 8); gap: calc(var(--spacing) * 8);
} }
.gap-10 {
gap: calc(var(--spacing) * 10);
}
.gap-\[1\.5px\] { .gap-\[1\.5px\] {
gap: 1.5px; gap: 1.5px;
} }
@@ -2132,6 +2147,9 @@
.bg-brand-navy2 { .bg-brand-navy2 {
background-color: #0b1525; background-color: #0b1525;
} }
.bg-brand-navy3\/60 {
background-color: color-mix(in oklab, #0f1e35 60%, transparent);
}
.bg-emerald-600 { .bg-emerald-600 {
background-color: var(--color-emerald-600); background-color: var(--color-emerald-600);
} }
@@ -5256,6 +5274,11 @@
padding: calc(var(--spacing) * 10); padding: calc(var(--spacing) * 10);
} }
} }
.md\:p-12 {
@media (width >= 48rem) {
padding: calc(var(--spacing) * 12);
}
}
.md\:px-6 { .md\:px-6 {
@media (width >= 48rem) { @media (width >= 48rem) {
padding-inline: calc(var(--spacing) * 6); padding-inline: calc(var(--spacing) * 6);
@@ -5434,6 +5457,11 @@
grid-template-columns: 1fr 240px; grid-template-columns: 1fr 240px;
} }
} }
.lg\:grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,420px\)\] {
@media (width >= 64rem) {
grid-template-columns: minmax(0,1fr) minmax(0,420px);
}
}
.lg\:flex-row { .lg\:flex-row {
@media (width >= 64rem) { @media (width >= 64rem) {
flex-direction: row; flex-direction: row;
@@ -5444,6 +5472,11 @@
gap: calc(var(--spacing) * 10); gap: calc(var(--spacing) * 10);
} }
} }
.lg\:gap-12 {
@media (width >= 64rem) {
gap: calc(var(--spacing) * 12);
}
}
.lg\:border-r { .lg\:border-r {
@media (width >= 64rem) { @media (width >= 64rem) {
border-right-style: var(--tw-border-style); border-right-style: var(--tw-border-style);

View File

@@ -1,11 +1,11 @@
{# Single source of truth for the v7.0 pricing — used by landing.html#tarifs and /tarifs page. {# Single source of truth for the v7.0 pricing — used by landing.html#tarifs and /tarifs page.
When prices change, edit ONLY this file (and src/billing/plans.py for Stripe IDs). When prices change, edit ONLY this file (and src/billing/plans.py for Stripe IDs).
v7.0 — 4 forfaits + 1 soumission : v7.0 — 3 forfaits Cloud (en rangée) + 1 DictIA LOCAL (bloc dédié) + 1 soumission :
- Cloud BASIC 189 $/mois (no setup) - Cloud BASIC 189 $/mois (no setup)
- Cloud ESSENTIEL 349 $/mois (no setup) - Cloud ESSENTIEL 349 $/mois (no setup)
- Cloud PRO 549 $/mois + 485 $ onboarding (recommended) - Cloud PRO 549 $/mois + 485 $ onboarding (recommended)
- DictIA LOCAL 5 998 $ An 1 puis 500 $/an dès An 2 (no monthly) - DictIA LOCAL 5 998 $ An 1 puis 500 $/an dès An 2 (bloc large dédié, "Vous en êtes propriétaire")
- Pro+ soumission personnalisée → /contact?pro-plus=1 - Pro+ soumission personnalisée → /contact?pro-plus=1
Common to all forfaits : Common to all forfaits :
@@ -13,6 +13,7 @@
exports SRT/VTT/TXT/JSON/DOCX, Loi 25 conforme, OVH Beauharnois (Cloud) ou local. #} exports SRT/VTT/TXT/JSON/DOCX, Loi 25 conforme, OVH Beauharnois (Cloud) ou local. #}
{% from 'macros/pricing_card.html' import pricing_card %} {% from 'macros/pricing_card.html' import pricing_card %}
{% from 'macros/button.html' import button %}
{%- set _baseline_features_cloud = [ {%- set _baseline_features_cloud = [
'WhisperX Large-v3 · 99&nbsp;%+ précision · 99+ langues', 'WhisperX Large-v3 · 99&nbsp;%+ précision · 99+ langues',
@@ -24,7 +25,8 @@
'Aucune limite utilisateurs' 'Aucune limite utilisateurs'
] -%} ] -%}
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6 items-stretch"> {# === Ligne 1 — 3 forfaits Cloud (1/2/3 cols responsive) === #}
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6 items-stretch">
{{ pricing_card( {{ pricing_card(
slug='cloud-basic', slug='cloud-basic',
@@ -70,32 +72,116 @@
cta_label='Commander Pro' cta_label='Commander Pro'
) }} ) }}
{{ pricing_card(
slug='dictia-local',
name='DictIA LOCAL',
badge='Local · 100&nbsp;% hors-ligne',
target='Confidentialité maximale · 100&nbsp;% hors-ligne chez vous.',
setup=5998,
yearly_renewal=500,
capacity_audio='~1&nbsp;100&nbsp;h audio/mois',
capacity_storage='2&nbsp;To SSD',
gpu='RTX 5070&nbsp;Ti 16&nbsp;Go (dédié)',
features=[
'WhisperX Large-v3 · 99&nbsp;%+ précision · 99+ langues',
'Diarisation pyannote (qui parle)',
'Résumés IA + Points d&rsquo;action (Mistral 7B local)',
'Exports SRT, VTT, TXT, JSON, DOCX',
'GPU local dédié · transcription locale',
'Données jamais sortantes (chez vous)',
'500&nbsp;$/an dès An&nbsp;2 (MAJ + support)',
'Aucune limite utilisateurs'
],
cta_label='Configurer DictIA Local'
) }}
</div> </div>
{# Pro+ banner — soumission personnalisée pour grands volumes / SLA renforcé #} {# === Bloc 2 — DictIA LOCAL (large, distinctif, pleine largeur) === #}
<section class="mt-12 relative overflow-hidden bg-brand-navy2 border border-brand-border rounded p-8 md:p-12"
aria-labelledby="dictia-local-title">
{# Decorative orbs background — purely decorative, hidden from AT #}
<div class="absolute -top-32 -right-32 w-[500px] h-[500px] rounded-full pointer-events-none"
style="background: radial-gradient(circle, rgba(37,99,235,0.10) 0%, transparent 70%); filter: blur(60px);" aria-hidden="true"></div>
<div class="absolute -bottom-32 -left-32 w-[400px] h-[400px] rounded-full pointer-events-none"
style="background: radial-gradient(circle, rgba(192,38,211,0.08) 0%, transparent 70%); filter: blur(60px);" aria-hidden="true"></div>
<div class="relative grid lg:grid-cols-[minmax(0,1fr)_minmax(0,420px)] gap-10 lg:gap-12 items-center">
{# === LEFT — copy + checkmarks === #}
<div>
<div class="flex items-center gap-2 mb-4 flex-wrap">
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-brand-b1/10 border border-brand-b1/30 text-xs font-semibold text-brand-b1">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-3.5 h-3.5" aria-hidden="true"><path d="M12 2C8 2 5 5 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-4-3-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
Au Québec
</span>
<span class="text-xs text-white/60">par InnovA AI</span>
</div>
<p class="eyebrow grad-text mb-2">DictIA LOCAL · Serveur souverain</p>
<h3 id="dictia-local-title" class="text-3xl md:text-4xl font-black text-white mb-4 leading-tight">
Vous en êtes <span class="grad-text">propriétaire</span>.
</h3>
<p class="text-base text-white/75 mb-6 leading-relaxed max-w-xl">
On vous vend, configure et installe votre serveur IA directement dans vos locaux. Vous êtes propriétaire du matériel. <strong class="text-white">Vos données ne quittent jamais votre bureau.</strong>
</p>
<ul class="space-y-3 mb-6" role="list">
{% for bullet in [
('PC + GPU RTX vous appartient', 'pas de location, pas d&rsquo;abonnement matériel'),
('Traitement 100&nbsp;% local', 'aucun transit réseau, fonctionne hors-ligne'),
('Assemblé et configuré au Québec par InnovA AI', 'support local inclus'),
('On vient l&rsquo;installer chez vous', 'formation incluse, opérationnel le jour&nbsp;1'),
('Achat direct sans appel d&rsquo;offres si &lt;&nbsp;34&nbsp;700&nbsp;$', 'DictIA LOCAL s&rsquo;y qualifie')
] %}
<li class="flex items-start gap-3 text-sm text-white/80">
<span class="flex-shrink-0 w-5 h-5 grad-bg flex items-center justify-center mt-0.5" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" class="w-3 h-3 text-white"><path d="M5 13l4 4L19 7"/></svg>
</span>
<span><strong class="text-white">{{ bullet[0] | safe }}</strong> — {{ bullet[1] | safe }}</span>
</li>
{% endfor %}
</ul>
<div class="flex flex-wrap gap-3 items-center">
{{ button('Voir les serveurs disponibles', href='/contact?plan=dictia-local', variant='primary', size='md', icon='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" aria-hidden="true"><path d="M14 5l7 7m0 0l-7 7m7-7H3"/></svg>') }}
<span class="text-sm text-white/60">5&nbsp;998&nbsp;$ An&nbsp;1 · 500&nbsp;$/an dès An&nbsp;2</span>
</div>
</div>
{# === RIGHT — server visual mockup === #}
<div class="relative">
<div class="bg-brand-navy3/60 border border-brand-b1/20 rounded p-6 backdrop-blur-sm">
<p class="eyebrow text-white/60 mb-3 text-center">GPU RTX — DictIA LOCAL</p>
<div class="flex flex-col items-center gap-4">
{# Server icon SVG (rack stylisé) — purely decorative #}
<div class="w-32 h-32 grad-bg flex items-center justify-center relative" aria-hidden="true">
<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.5" class="w-16 h-16 text-white">
<rect x="8" y="10" width="48" height="14" rx="0"/>
<circle cx="14" cy="17" r="1.5" fill="currentColor"/>
<circle cx="20" cy="17" r="1.5" fill="currentColor"/>
<line x1="30" y1="17" x2="50" y2="17"/>
<rect x="8" y="28" width="48" height="14" rx="0"/>
<circle cx="14" cy="35" r="1.5" fill="currentColor"/>
<circle cx="20" cy="35" r="1.5" fill="currentColor"/>
<line x1="30" y1="35" x2="50" y2="35"/>
<rect x="8" y="46" width="48" height="14" rx="0"/>
<circle cx="14" cy="53" r="1.5" fill="currentColor"/>
<circle cx="20" cy="53" r="1.5" fill="currentColor"/>
<line x1="30" y1="53" x2="50" y2="53"/>
</svg>
</div>
<p class="text-center text-white font-bold text-base">Serveur DictIA</p>
<ul class="space-y-1.5 text-xs text-white/70 w-full" role="list">
<li class="flex items-center gap-2">
<span class="w-1 h-1 rounded-full bg-brand-b2" aria-hidden="true"></span>
<span>Interface web</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1 h-1 rounded-full bg-brand-b2" aria-hidden="true"></span>
<span>PC gaming haute performance</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1 h-1 rounded-full bg-brand-b2" aria-hidden="true"></span>
<span>GPU RTX 5070&nbsp;Ti 16&nbsp;Go dédié IA</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1 h-1 rounded-full bg-brand-b2" aria-hidden="true"></span>
<span>WhisperX + LLM Mistral 7B local</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1 h-1 rounded-full bg-brand-b2" aria-hidden="true"></span>
<span>DictIA pré-installé</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1 h-1 rounded-full bg-brand-b3" aria-hidden="true"></span>
<span class="font-semibold text-white">Votre propriété</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</section>
{# === Pro+ banner — soumission personnalisée pour grands volumes / SLA renforcé === #}
<div class="mt-10 max-w-5xl mx-auto p-6 bg-brand-navy text-white border border-brand-b2/30 rounded backdrop-blur-sm relative overflow-hidden"> <div class="mt-10 max-w-5xl mx-auto p-6 bg-brand-navy text-white border border-brand-b2/30 rounded backdrop-blur-sm relative overflow-hidden">
<div class="absolute inset-0 pointer-events-none opacity-60" aria-hidden="true" <div class="absolute inset-0 pointer-events-none opacity-60" aria-hidden="true"
style="background: radial-gradient(circle at 100% 0%, rgba(192,38,211,0.12) 0%, transparent 55%), radial-gradient(circle at 0% 100%, rgba(6,182,212,0.10) 0%, transparent 55%);"></div> style="background: radial-gradient(circle at 100% 0%, rgba(192,38,211,0.12) 0%, transparent 55%), radial-gradient(circle at 0% 100%, rgba(6,182,212,0.10) 0%, transparent 55%);"></div>
@@ -107,7 +193,6 @@
&gt;&nbsp;660&nbsp;h audio/mois · &gt;&nbsp;500&nbsp;Go stockage · 7+ utilisateurs intensifs · multi-sites · SLA&nbsp;99,9&nbsp;% · SOC&nbsp;2 Type&nbsp;I/II · PHIPA · PIPEDA Ontario · documentation gouv. (SEAO/MCN). &gt;&nbsp;660&nbsp;h audio/mois · &gt;&nbsp;500&nbsp;Go stockage · 7+ utilisateurs intensifs · multi-sites · SLA&nbsp;99,9&nbsp;% · SOC&nbsp;2 Type&nbsp;I/II · PHIPA · PIPEDA Ontario · documentation gouv. (SEAO/MCN).
</p> </p>
</div> </div>
{% from 'macros/button.html' import button %}
{{ button('Demander une soumission', href='/contact?pro-plus=1', variant='primary', size='md') }} {{ button('Demander une soumission', href='/contact?pro-plus=1', variant='primary', size='md') }}
</div> </div>
</div> </div>

View File

@@ -1,7 +1,7 @@
{% extends 'marketing/base.html' %} {% extends 'marketing/base.html' %}
{% block title %}Tarifs DictIA — 4 forfaits transparents en CAD (Cloud Basic 189 $/mo · Essentiel 349 $ · Pro 549 $ · DictIA Local 5 998 $){% endblock %} {% block title %}Tarifs DictIA — 3 Cloud + 1 serveur Local en CAD (Cloud Basic 189 $/mo · Essentiel 349 $ · Pro 549 $ · DictIA LOCAL 5 998 $){% endblock %}
{% block description %}Tarifs DictIA en CAD : Cloud Basic (189 $/mo), Cloud Essentiel (349 $/mo), Cloud Pro (549 $/mo + 485 $ onboarding) et DictIA Local (5 998 $ An 1 puis 500 $/an). Aucune limite utilisateurs, taxes en sus.{% endblock %} {% block description %}Tarifs DictIA en CAD : Cloud Basic (189 $/mo), Cloud Essentiel (349 $/mo), Cloud Pro (549 $/mo + 485 $ onboarding) et DictIA LOCAL (5 998 $ An 1 puis 500 $/an, vous en êtes propriétaire). Aucune limite utilisateurs, taxes en sus.{% endblock %}
{% block content %} {% block content %}
@@ -10,18 +10,18 @@
<div class="max-w-[820px] mx-auto px-6 text-center"> <div class="max-w-[820px] mx-auto px-6 text-center">
<p class="eyebrow grad-text mb-4">TARIFS</p> <p class="eyebrow grad-text mb-4">TARIFS</p>
<h1 id="page-title" class="text-[clamp(2.25rem,4vw,3.5rem)] font-black mb-4"> <h1 id="page-title" class="text-[clamp(2.25rem,4vw,3.5rem)] font-black mb-4">
Quatre forfaits&nbsp;: <span class="grad-text">choisissez votre infrastructure</span>. Trois forfaits Cloud + DictIA&nbsp;LOCAL&nbsp;: <span class="grad-text">choisissez votre infrastructure</span>.
</h1> </h1>
<p class="text-lg text-white/80"> <p class="text-lg text-white/80">
3 Cloud souverains au Québec + 1 100&nbsp;% local hors-ligne. Aucune limite utilisateurs, tarifs en CAD, taxes en sus (TPS&nbsp;5&nbsp;% + TVQ&nbsp;9,975&nbsp;%). 3 Cloud souverains hébergés au Québec + 1 serveur 100&nbsp;% local dont vous êtes propriétaire. Aucune limite utilisateurs, tarifs en CAD, taxes en sus (TPS&nbsp;5&nbsp;% + TVQ&nbsp;9,975&nbsp;%).
</p> </p>
</div> </div>
</section> </section>
{# ===== 4 PRICING TIERS + Pro+ ===== #} {# ===== 3 Cloud + DictIA LOCAL block + Pro+ ===== #}
<section class="bg-brand-bg py-20" aria-labelledby="forfaits-title"> <section class="bg-brand-bg py-20" aria-labelledby="forfaits-title">
<div class="max-w-[1200px] mx-auto px-6"> <div class="max-w-[1200px] mx-auto px-6">
<h2 id="forfaits-title" class="sr-only">Quatre forfaits DictIA + Pro+ sur soumission</h2> <h2 id="forfaits-title" class="sr-only">Trois forfaits Cloud DictIA + DictIA LOCAL + Pro+ sur soumission</h2>
{% include 'marketing/_partials/_pricing_tiers.html' %} {% include 'marketing/_partials/_pricing_tiers.html' %}
</div> </div>
</section> </section>

View File

@@ -360,28 +360,32 @@ def test_pricing_recommended_tier_is_cloud_pro():
def test_pricing_cta_labels_v7(): def test_pricing_cta_labels_v7():
"""CTAs reflect v7.0 forfait choice (Démarrer en Cloud / Choisir Essentiel / Commander Pro / Configurer DictIA Local).""" """CTAs reflect v7.0 forfait choice — 3 Cloud go to /checkout, DictIA LOCAL goes to /contact?plan=dictia-local."""
client = app.test_client() client = app.test_client()
body = client.get('/').data.decode('utf-8') body = client.get('/').data.decode('utf-8')
for slug in ['cloud-basic', 'cloud-essentiel', 'cloud-pro', 'dictia-local']: # 3 Cloud forfaits use /checkout/<slug>
for slug in ['cloud-basic', 'cloud-essentiel', 'cloud-pro']:
assert f'href="/checkout/{slug}"' in body, f"Missing checkout link for {slug}" assert f'href="/checkout/{slug}"' in body, f"Missing checkout link for {slug}"
# CTA labels match the macro callers in _pricing_tiers.html # CTA labels match the macro callers in _pricing_tiers.html
assert 'Démarrer en Cloud' in body or 'D&eacute;marrer en Cloud' in body assert 'Démarrer en Cloud' in body or 'D&eacute;marrer en Cloud' in body
assert 'Choisir Essentiel' in body assert 'Choisir Essentiel' in body
assert 'Commander Pro' in body assert 'Commander Pro' in body
assert 'Configurer DictIA Local' in body # DictIA LOCAL now has a dedicated block with a contact CTA (no /checkout slug)
assert 'Voir les serveurs disponibles' in body, "Missing DictIA LOCAL block CTA label"
assert '/contact?plan=dictia-local' in body, "Missing DictIA LOCAL contact CTA href"
def test_pricing_features_use_safe_filter_no_double_escape(): def test_pricing_features_use_safe_filter_no_double_escape():
"""Pricing card features piped through | safe — '&nbsp;' must render single-escaped, not double.""" """Pricing card features piped through | safe — '&nbsp;' must render single-escaped, not double."""
client = app.test_client() client = app.test_client()
body = client.get('/').data.decode('utf-8') body = client.get('/').data.decode('utf-8')
# Capacity chips use NBSP # Capacity chips use NBSP (3 Cloud forfaits)
assert '~165&nbsp;h audio/mois' in body, "Missing Cloud BASIC capacity chip" assert '~165&nbsp;h audio/mois' in body, "Missing Cloud BASIC capacity chip"
assert '100&nbsp;Go' in body, "Missing Cloud BASIC storage chip" assert '100&nbsp;Go' in body, "Missing Cloud BASIC storage chip"
assert '~660&nbsp;h audio/mois' in body, "Missing Cloud PRO capacity chip" assert '~660&nbsp;h audio/mois' in body, "Missing Cloud PRO capacity chip"
assert '500&nbsp;Go' in body, "Missing Cloud PRO storage chip" assert '500&nbsp;Go' in body, "Missing Cloud PRO storage chip"
assert '2&nbsp;To SSD' in body, "Missing DictIA LOCAL storage chip" # DictIA LOCAL dedicated block — server visual mockup uses NBSP for GPU spec
assert 'RTX 5070&nbsp;Ti 16&nbsp;Go' in body, "Missing DictIA LOCAL GPU spec line in dedicated block"
# WhisperX precision claim w/ NBSP # WhisperX precision claim w/ NBSP
assert 'WhisperX Large-v3' in body, "Missing WhisperX Large-v3 mention" assert 'WhisperX Large-v3' in body, "Missing WhisperX Large-v3 mention"
# Loi 25 with NBSP # Loi 25 with NBSP
@@ -462,8 +466,9 @@ def test_pricing_cta_url_no_double_slash():
"""pricing_card uses cta_url.rstrip('/') so href never has '//' (regression guard).""" """pricing_card uses cta_url.rstrip('/') so href never has '//' (regression guard)."""
client = app.test_client() client = app.test_client()
body = client.get('/').data.decode('utf-8') body = client.get('/').data.decode('utf-8')
# All 4 CTAs use the default cta_url='/checkout' (no trailing slash) — so /checkout/<slug> # The 3 Cloud forfaits use the default cta_url='/checkout' (no trailing slash) — so /checkout/<slug>
for slug in ['cloud-basic', 'cloud-essentiel', 'cloud-pro', 'dictia-local']: # DictIA LOCAL is presented in a dedicated block with /contact?plan=dictia-local (not via pricing_card macro)
for slug in ['cloud-basic', 'cloud-essentiel', 'cloud-pro']:
assert f'href="/checkout/{slug}"' in body, f"Missing single-slash href for {slug}" assert f'href="/checkout/{slug}"' in body, f"Missing single-slash href for {slug}"
assert f'href="/checkout//{slug}"' not in body, f"Double-slash regression for {slug}" assert f'href="/checkout//{slug}"' not in body, f"Double-slash regression for {slug}"

View File

@@ -34,7 +34,7 @@ def test_tarifs_has_h1_with_anchor():
def test_tarifs_renders_4_pricing_cards_v7(): def test_tarifs_renders_4_pricing_cards_v7():
"""Tarifs page renders the v7.0 4 forfaits + Pro+ chip.""" """Tarifs page renders the v7.0 3 Cloud forfaits + DictIA LOCAL dedicated block + Pro+ chip."""
client = app.test_client() client = app.test_client()
body = client.get('/tarifs').data.decode('utf-8') body = client.get('/tarifs').data.decode('utf-8')
for tier in ['Cloud BASIC', 'Cloud ESSENTIEL', 'Cloud PRO', 'DictIA LOCAL']: for tier in ['Cloud BASIC', 'Cloud ESSENTIEL', 'Cloud PRO', 'DictIA LOCAL']:
@@ -45,11 +45,15 @@ def test_tarifs_renders_4_pricing_cards_v7():
assert '549&nbsp;$' in body assert '549&nbsp;$' in body
assert '485&nbsp;$' in body # Cloud Pro onboarding assert '485&nbsp;$' in body # Cloud Pro onboarding
assert '5&nbsp;998&nbsp;$' in body # DictIA Local An 1 assert '5&nbsp;998&nbsp;$' in body # DictIA Local An 1
# Checkout slugs # 3 Cloud forfaits use checkout slugs
assert 'href="/checkout/cloud-basic"' in body assert 'href="/checkout/cloud-basic"' in body
assert 'href="/checkout/cloud-essentiel"' in body assert 'href="/checkout/cloud-essentiel"' in body
assert 'href="/checkout/cloud-pro"' in body assert 'href="/checkout/cloud-pro"' in body
assert 'href="/checkout/dictia-local"' in body # DictIA LOCAL has its own dedicated block with a contact CTA (no /checkout slug)
assert '/contact?plan=dictia-local' in body, "Missing DictIA LOCAL block contact CTA"
assert 'Vous en êtes' in body or 'Vous en &ecirc;tes' in body, \
"Missing DictIA LOCAL block headline 'Vous en êtes propriétaire'"
assert 'Serveur DictIA' in body, "Missing DictIA LOCAL block server visual mockup label"
# Pro+ chip with /contact link # Pro+ chip with /contact link
assert 'Pro+' in body assert 'Pro+' in body
assert '/contact?pro-plus=1' in body assert '/contact?pro-plus=1' in body