- {{ page.title }}{% if page.external %}↗{% endif %}
+ {{ page.title }}{% if page.external %}{% endif %}
{{ page.description }}
{% if page.external %}
-
- →
+
+
{{ page.url }}(s'ouvre dans un nouvel onglet)
diff --git a/templates/macros/bento.html b/templates/macros/bento.html
index c05f84a..3fd8687 100644
--- a/templates/macros/bento.html
+++ b/templates/macros/bento.html
@@ -1,12 +1,15 @@
{# Reusable bento card macro. FlexiHub style: dark navy2 surface, decorative watermark number, gradient icon corner.
`span` controls column span via a static lookup table (Tailwind's content scanner only sees literal class strings,
- so dynamic `col-span-{{ span }}` would produce dead classes — the lookup keeps the utilities discoverable). #}
-{% macro bento_card(number, title, description, icon='✦', span='1') %}
+ so dynamic `col-span-{{ span }}` would produce dead classes — the lookup keeps the utilities discoverable).
+ `icon` is rendered via `| safe` so callers can pass either inline SVG markup (preferred) or a plain string.
+ The default is a small inline sparkle SVG to avoid any emoji fallback. #}
+{% macro bento_card(number, title, description, icon=None, span='1') %}
{%- set span_classes = {'1': 'col-span-1', '2': 'sm:col-span-2', '3': 'sm:col-span-2 md:col-span-3'} -%}
+{%- set default_icon = '' -%}
{% from 'macros/button.html' import button %}
- {{ button('Demander un dossier conformité', href='mailto:info@dictia.ca?subject=Demande%20dossier%20conformit%C3%A9', variant='primary', size='lg', icon='✉️') }}
+ {{ button('Demander un dossier conformité', href='mailto:info@dictia.ca?subject=Demande%20dossier%20conformit%C3%A9', variant='primary', size='lg', icon='') }}
{{ button('Voir les forfaits', href='/tarifs', variant='secondary', size='lg') }}
diff --git a/templates/marketing/contact.html b/templates/marketing/contact.html
index ee38b3a..1352c58 100644
--- a/templates/marketing/contact.html
+++ b/templates/marketing/contact.html
@@ -13,7 +13,7 @@
Parlons de votre projet.
- Réponse sous 2 jours ouvrables. Pour les urgences techniques des clients existants, voyez la console DictIA → Support.
+ Réponse sous 2 jours ouvrables. Pour les urgences techniques des clients existants, voyez la section Support de la console DictIA.
@@ -26,7 +26,9 @@
{# Email card #}
-
✉️
+
+
+
Courriel
Privilégiez le courriel pour : pré-inscription, devis, démonstration, dossier de conformité, partenariats.
@@ -36,7 +38,9 @@
{# Phone card #}
-
☎️
+
+
+
Téléphone
Du lundi au vendredi, 9 h à 17 h (heure de l'Est). Laissez un message en dehors de ces heures.
@@ -46,7 +50,9 @@
{# Mailing address card #}
-
📬
+
+
+
Bureau
Sur rendez-vous uniquement. Visites en personne pour démonstrations on-premise et déploiements DictIA 16 corporatifs.
@@ -73,18 +79,25 @@
+ {# Shortcut icons (heroicons-style outline). Each is a self-contained inline SVG passed via | safe in the loop. #}
+ {%- set svg_target = '' -%}
+ {%- set svg_office = '' -%}
+ {%- set svg_play = '' -%}
+ {%- set svg_scale_sm = '' -%}
+ {%- set svg_handshake = '' -%}
+ {%- set svg_news = '' -%}
{# NOTE: bento card content is duplicated between landing.html and fonctionnalites.html.
- When editing, sync both files. Future refactor: extract to _partials/_bento_features.html. #}
+ When editing, sync both files. Future refactor: extract to _partials/_bento_features.html.
+ Icon SVGs (heroicons-style outline) are inlined directly because the macro renders `icon | safe`. #}
{% from 'macros/bento.html' import bento_card %}
+ {%- set icon_microphone = '' -%}
+ {%- set icon_users = '' -%}
+ {%- set icon_document = '' -%}
+ {%- set icon_chat = '' -%}
+ {%- set icon_export = '' -%}
+ {%- set icon_plug = '' -%}
- {{ bento_card('01', 'Transcription WhisperX', 'Large-v3 fine-tuné FR-CA. Précision 95 %+ sur réunions, dictées, audiences (méthodologie disponible sur demande).', '🎙️') }}
- {{ bento_card('02', 'Diarisation 8 locuteurs', 'pyannote sépare automatiquement les intervenants. Identification par embeddings vocaux.', '👥') }}
- {{ bento_card('03', 'Résumés Mistral 7B', 'IA locale génère résumés, points d\'action et procès-verbaux. Aucune connexion cloud.', '📝') }}
- {{ bento_card('04', 'Q&R sur enregistrement', 'Posez des questions à vos réunions. RAG local sur embeddings sentence-transformers.', '💬') }}
- {{ bento_card('05', 'Exports multiples', 'DOCX, PDF, SRT, VTT, TXT, JSON, MD. Formats avocat, notaire, CPA.', '📄') }}
- {{ bento_card('06', 'Intégrations', 'Word, Outlook, Teams, Notion, Obsidian, Zapier, Make, n8n.', '🔌') }}
+ {{ bento_card('01', 'Transcription WhisperX', 'Large-v3 fine-tuné FR-CA. Précision 95 %+ sur réunions, dictées, audiences (méthodologie disponible sur demande).', icon_microphone) }}
+ {{ bento_card('02', 'Diarisation 8 locuteurs', 'pyannote sépare automatiquement les intervenants. Identification par embeddings vocaux.', icon_users) }}
+ {{ bento_card('03', 'Résumés Mistral 7B', 'IA locale génère résumés, points d\'action et procès-verbaux. Aucune connexion cloud.', icon_document) }}
+ {{ bento_card('04', 'Q&R sur enregistrement', 'Posez des questions à vos réunions. RAG local sur embeddings sentence-transformers.', icon_chat) }}
+ {{ bento_card('05', 'Exports multiples', 'DOCX, PDF, SRT, VTT, TXT, JSON, MD. Formats avocat, notaire, CPA.', icon_export) }}
+ {{ bento_card('06', 'Intégrations', 'Word, Outlook, Teams, Notion, Obsidian, Zapier, Make, n8n.', icon_plug) }}
@@ -137,7 +144,7 @@
{% from 'macros/button.html' import button %}
- {{ button('Pré-inscription par courriel', href='mailto:info@dictia.ca?subject=Pré-inscription%20DictIA', variant='primary', size='lg', icon='✉️') }}
+ {{ button('Pré-inscription par courriel', href='mailto:info@dictia.ca?subject=Pré-inscription%20DictIA', variant='primary', size='lg', icon='') }}
{{ button('Voir les tarifs', href='/tarifs', variant='secondary', size='lg') }}
diff --git a/templates/marketing/landing.html b/templates/marketing/landing.html
index 2de342b..381e978 100644
--- a/templates/marketing/landing.html
+++ b/templates/marketing/landing.html
@@ -20,7 +20,7 @@
{# Subtle grid overlay (FlexiHub signature) #}
- {# Horizontal accent line — gradient blue→cyan→transparent #}
+ {# Horizontal accent line — gradient blue to cyan to transparent #}
@@ -47,7 +47,7 @@
{% from 'macros/button.html' import button %}
{{ button('Réserver une démo', href='/contact', variant='primary', size='lg') }}
- {{ button('Voir les tarifs →', href='/tarifs', variant='ghost', size='lg') }}
+ {{ button('Voir les tarifs', href='/tarifs', variant='ghost', size='lg', icon='') }}
{# Social proof microcopy — defensible: refers to pre-launch waitlist + factual ordres pros count #}
@@ -136,12 +136,15 @@
{# 3 problem cards on white surface — Cloud Act, Loi 25, Sanctions #}
{% for card in [
- ('Cloud Act', 'Loi américaine 2018', 'Microsoft, Google et OpenAI sont soumis au Cloud Act. Vos données peuvent être saisies par les autorités américaines sans votre consentement ni notification — y compris les enregistrements de vos réunions client.', '⚖️'),
- ('Loi 25 — biométrie', 'Sanctions CAI jusqu\'à 25 M$', 'La voix est une donnée biométrique au sens des articles 44-45 de la LCCJTI et un renseignement sensible au sens de la Loi 25 (art. 12 LSP). Tout traitement nécessite un consentement explicite, une déclaration préalable à la CAI et un transfert vers un territoire offrant une protection équivalente — ce que les États-Unis n\'offrent pas.', '🛡️'),
- ('Sanctions disciplinaires', '~250 000 pros réglementés QC (CIQ)', 'Les ordres professionnels québécois — au premier rang desquels le Barreau, la Chambre des notaires et CPA Québec — exigent une obligation stricte de confidentialité. Le transfert de données client hors-juridiction sans consentement explicite peut être qualifié de manquement, jusqu\'à la radiation pour les fautes graves.', '⚠️')
+ ('Cloud Act', 'Loi américaine 2018', 'Microsoft, Google et OpenAI sont soumis au Cloud Act. Vos données peuvent être saisies par les autorités américaines sans votre consentement ni notification — y compris les enregistrements de vos réunions client.',
+ ''),
+ ('Loi 25 — biométrie', 'Sanctions CAI jusqu\'à 25 M$', 'La voix est une donnée biométrique au sens des articles 44-45 de la LCCJTI et un renseignement sensible au sens de la Loi 25 (art. 12 LSP). Tout traitement nécessite un consentement explicite, une déclaration préalable à la CAI et un transfert vers un territoire offrant une protection équivalente — ce que les États-Unis n\'offrent pas.',
+ ''),
+ ('Sanctions disciplinaires', '~250 000 pros réglementés QC (CIQ)', 'Les ordres professionnels québécois — au premier rang desquels le Barreau, la Chambre des notaires et CPA Québec — exigent une obligation stricte de confidentialité. Le transfert de données client hors-juridiction sans consentement explicite peut être qualifié de manquement, jusqu\'à la radiation pour les fautes graves.',
+ '')
] %}
-
{% for card in [
{
- 'icon': '🍁',
+ 'icon': svg_pin,
'title': 'Hébergement OVH Beauharnois',
'desc': 'Centre de données opéré par OVHcloud Canada en territoire québécois. Conformité documentée selon les services (ISO 27001, SOC 2 selon le périmètre). Détails sur demande.'
},
{
- 'icon': '⚖️',
+ 'icon': svg_scale,
'title': 'Mappé Loi 25 (LPRPSP)',
'desc': 'Audit trail art. 3.5, EFVP préparée art. 3.3, registre des consentements art. 14. Modèles de déclaration CAI fournis.'
},
{
- 'icon': '🏛️',
+ 'icon': svg_building,
'title': 'Compatible Cadre IA secteur public',
'desc': 'DictIA est conçu pour s\'inscrire dans le cadre de gestion des systèmes d\'IA du secteur public québécois (LGGRI). Documentation détaillée sur demande.'
},
{
- 'icon': '🔓',
+ 'icon': svg_code,
'title': 'Code source AGPL v3 vérifiable',
'desc': 'Architecture entièrement auditable sur Gitea public. Aucune boîte noire. Vos auditeurs peuvent examiner chaque ligne.'
}
] %}
-
{{ card.icon }}
+
{{ card.icon | safe }}
{{ card.title | safe }}
{{ card.desc | safe }}
@@ -423,10 +463,13 @@
{% for t in testimonials %}
-
{% for item in [
{'q': 'Y a-t-il des frais cachés?', 'a': 'Non. Les tarifs affichés couvrent l\'utilisation illimitée (volume audio, utilisateurs, exports). Les seules variables sont : les taxes (TPS 5 % + TVQ 9,975 %) et, pour DictIA on-premise, le matériel GPU si vous ne l\'avez pas déjà. Aucun frais par minute, par mot, par locuteur.'},
- {'q': 'Puis-je passer d\'un forfait à un autre?', 'a': 'Oui, en tout temps. Les passages DictIA Cloud → on-premise et inversement sont supportés. Les données peuvent être migrées sur demande, sans frais. Détails dans nos conditions d\'utilisation.'},
+ {'q': 'Puis-je passer d\'un forfait à un autre?', 'a': 'Oui, en tout temps. Les passages de DictIA Cloud vers on-premise et inversement sont supportés. Les données peuvent être migrées sur demande, sans frais. Détails dans nos conditions d\'utilisation.'},
{'q': 'Le tarif on-premise inclut-il le matériel GPU?', 'a': 'Le tarif setup (3 450 $ pour DictIA 8 ou 5 750 $ pour DictIA 16) inclut l\'installation logicielle complète, la configuration sécurité, la formation et 90 jours de support prioritaire. Le matériel GPU n\'est pas inclus ; nous fournissons une liste de cartes RTX recommandées (RTX 4060 8 Go pour DictIA 8, RTX 4080/5080 16 Go pour DictIA 16) et pouvons faire l\'achat pour vous moyennant marge transparente.'},
{'q': 'Comment fonctionne la facturation TPS/TVQ?', 'a': 'DictIA Inc. est inscrite TPS et TVQ. Les factures détaillent les taxes selon votre province de facturation. Pour les organismes exemptés (organismes publics, etc.), envoyez votre attestation à info@dictia.ca avant l\'inscription.'},
{'q': 'Existe-t-il un tarif annuel ou pluriannuel?', 'a': 'Disponible sur demande pour les engagements 12 ou 24 mois (remise typique de 10 à 15 %). Écrivez à info@dictia.ca pour un devis.'}
@@ -127,7 +128,7 @@
{% from 'macros/button.html' import button %}
- {{ button('Discuter avec notre équipe', href='mailto:info@dictia.ca?subject=Question%20tarifs%20DictIA', variant='primary', size='lg', icon='✉️') }}
+ {{ button('Discuter avec notre équipe', href='mailto:info@dictia.ca?subject=Question%20tarifs%20DictIA', variant='primary', size='lg', icon='') }}
{{ button('Voir les fonctionnalités', href='/fonctionnalites', variant='ghost', size='lg') }}
diff --git a/tests/test_marketing_landing_template.py b/tests/test_marketing_landing_template.py
index ddabb75..b20f7b3 100644
--- a/tests/test_marketing_landing_template.py
+++ b/tests/test_marketing_landing_template.py
@@ -611,18 +611,28 @@ def test_no_unverifiable_competitor_claims():
def test_comparatif_check_marks_consistently_mean_good():
- """Each ✓ in the table should mark a 'good' outcome for that column.
+ """Status SVGs in the comparatif must mark each cell with the right semantic.
Regression guard against the inverted-Cloud-Act-row bug.
- Specifically: DictIA cell of every row must contain ✓ (DictIA wins on every criterion).
+
+ Since the visual marker (✓ / ✗ / ⚠) was migrated to inline SVGs (no emoji policy),
+ we assert on (a) the row label, (b) each Teams cell wraps "Soumis Cloud Act" with
+ the red 'Non conforme' SVG, and (c) DictIA cell uses the green 'Conforme' SVG.
"""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
- # The 'Souveraineté hors Cloud Act' row must have ✓ for DictIA (after rename)
+ # The 'Souveraineté hors Cloud Act' row must remain (after rename)
assert 'Souveraineté hors Cloud Act' in body
# And must NOT have the legacy inverted form
assert 'Exposé au Cloud Act' not in body, "Row 2 must be reworded to positive convention"
- # Specifically check Teams gets ✗ for the territoriality criterion (was ⚠ before)
- assert '✗ Soumis Cloud Act' in body, "Teams must show ✗ for non-Loi-25-compliant transfer"
+ # Teams cell for the territoriality criterion: must include the "Non conforme" SVG
+ # immediately followed by the "Soumis Cloud Act" label.
+ assert 'Soumis Cloud Act' in body, "Row 1 Teams cell must say 'Soumis Cloud Act'"
+ assert 'aria-label="Non conforme"' in body, \
+ "X-mark SVG with aria-label='Non conforme' must be present (Teams ✗)"
+ assert 'aria-label="Conforme"' in body, \
+ "Check SVG with aria-label='Conforme' must be present (DictIA ✓)"
+ assert 'aria-label="Partiel"' in body, \
+ "Warning SVG with aria-label='Partiel' must be present (⚠ rows)"
def test_testimonials_section_present_with_placeholder_cards():