refactor(ui): éliminer tous les emojis (SVG inline + texte propre, look pro/moderne)

Pass de modernisation visuelle : remplacement de TOUS les emojis Unicode dans
les templates marketing/legal/billing/auth par des SVG inline (style heroicons)
ou par du texte propre, pour un look SaaS pro à la Linear/Vercel/Stripe.

Mapping principal :
- ✓ / ✗ / ⚠           → SVG check / x / triangle (text-brand-b3 / red / amber)
- → / ← / ↗            → SVG arrow-right / arrow-left / arrow-up-right
- 🍁 / 🏛️ / ⚖️ / 🔓     → SVG map-pin / building / scale / code-brackets
- 🎙️ / 👥 / 📝 / 💬 / 📄 / 🔌 → 6 SVG bento icons (microphone, users, doc, chat, export, plug)
- ✉️ / ☎️ / 📬          → SVG envelope / phone / map-pin
- ↺                    → SVG refresh-counter-clockwise
- ★                    → SVG star (RECOMMANDÉ badge)
- 🎯/🏢/📺/🤝/📰         → SVG target / office / play / handshake / news (raccourcis contact)
- ⚖️/📊/🏛️ (testimonials) → SVG scale / bar-chart / building
- ✦ (default bento icon) → SVG sparkle inline

Tous les SVG utilisent stroke="currentColor" pour héritage Tailwind text-*.
Les SVG informationnels du tableau comparatif portent un aria-label sémantique
(Conforme/Non conforme/Partiel) ; les SVG décoratifs portent aria-hidden.

Tests :
- 18/18 legal pages passent (test_legal_pages.py)
- test_comparatif_check_marks_consistently_mean_good ajusté pour asserter
  sur les aria-label SVG plutôt que les caractères ✓/✗
- 4 échecs pré-existants non liés (manque /blog dans nav, SOC 2 hedge dans
  conformite.html, gitea.innova-ai.ca url) — confirmés présents avant ce commit

Fichiers modifiés (14) :
- templates/macros/{bento,pricing_card}.html (sources de vérité)
- templates/marketing/{base,_footer,landing,fonctionnalites,tarifs,conformite,contact}.html
- templates/legal/{_layout,index}.html
- templates/billing/{cancel,success}.html
- tests/test_marketing_landing_template.py (assert sur aria-label)

Audit final : 0 emoji restant dans les fichiers in-scope ; 0 emoji dans le
HTML rendu de toutes les pages marketing/legal vérifiées.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Allison
2026-04-28 10:52:58 -04:00
parent f83fdfcd68
commit 8d50d8ee01
14 changed files with 199 additions and 111 deletions

View File

@@ -20,7 +20,7 @@
{# Subtle grid overlay (FlexiHub signature) #}
<div class="absolute inset-0"
style="background-image: linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px); background-size: 40px 40px;"></div>
{# Horizontal accent line — gradient blue→cyan→transparent #}
{# Horizontal accent line — gradient blue to cyan to transparent #}
<div class="absolute top-1/3 left-0 right-0 h-px"
style="background: linear-gradient(90deg, transparent, rgba(0,98,255,0.3), rgba(0,189,216,0.2), transparent);"></div>
</div>
@@ -47,7 +47,7 @@
<div class="flex flex-col sm:flex-row gap-3 justify-center animate-tc-fade-in-up" style="animation-delay: 300ms; animation-fill-mode: backwards;">
{% 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='<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>') }}
</div>
{# 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 #}
<div class="grid md:grid-cols-3 gap-6 mt-12">
{% 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&nbsp;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.&nbsp;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&nbsp;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.',
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6" aria-hidden="true"><path d="M12 3v18"/><path d="M5 7h14"/><path d="M5 7l-2 6a4 4 0 0 0 8 0L9 7"/><path d="M19 7l2 6a4 4 0 0 1-8 0l2-6"/><path d="M8 21h8"/></svg>'),
('Loi 25 — biométrie', 'Sanctions CAI jusqu\'à 25&nbsp;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.&nbsp;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.',
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>'),
('Sanctions disciplinaires', '~250&nbsp;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.',
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>')
] %}
<article class="bg-white p-6 rounded border border-brand-border">
<div class="text-3xl mb-4" aria-hidden="true">{{ card[3] }}</div>
<div class="grad-text mb-4" aria-hidden="true">{{ card[3] | safe }}</div>
<h3 class="text-lg font-bold mb-1 text-brand-navy">{{ card[0] }}</h3>
<p class="text-xs uppercase tracking-wider text-brand-navy/70 mb-3 font-semibold">{{ card[1] | safe }}</p>
<p class="text-sm text-brand-navy/70 leading-relaxed">{{ card[2] | safe }}</p>
@@ -176,7 +179,9 @@
('Précision FR-CA', 'WhisperX Large-v3 fine-tuné français québécois. Diarisation pyannote 8 locuteurs. Résumés Mistral 7B local — aucune connexion OpenAI/Google/Microsoft.')
] %}
<article class="bg-white/[0.05] backdrop-blur-sm p-6 rounded border border-white/[0.08]">
<div class="w-10 h-10 grad-bg rounded-none mb-4 flex items-center justify-center font-black text-white shadow-cta" aria-hidden="true"></div>
<div class="w-10 h-10 grad-bg rounded-none mb-4 flex items-center justify-center text-white shadow-cta" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5"><path d="M5 13l4 4L19 7"/></svg>
</div>
<h3 class="text-lg font-bold mb-2 text-white">{{ pillar[0] | safe }}</h3>
<p class="text-sm text-white/70 leading-relaxed">{{ pillar[1] | safe }}</p>
</article>
@@ -196,15 +201,22 @@
</div>
{# 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 = '<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-5 h-5" aria-hidden="true"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="22"/></svg>' -%}
{%- set icon_users = '<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-5 h-5" aria-hidden="true"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>' -%}
{%- set icon_document = '<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-5 h-5" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/></svg>' -%}
{%- set icon_chat = '<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-5 h-5" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>' -%}
{%- set icon_export = '<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-5 h-5" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>' -%}
{%- set icon_plug = '<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-5 h-5" aria-hidden="true"><path d="M9 2v6"/><path d="M15 2v6"/><path d="M5 8h14v4a5 5 0 0 1-5 5h-4a5 5 0 0 1-5-5z"/><path d="M12 17v5"/></svg>' -%}
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-[1.5px] bg-brand-border rounded overflow-hidden">
{{ bento_card('01', 'Transcription WhisperX', 'Large-v3 fine-tuné FR-CA. Précision 95&nbsp;%+ 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&amp;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&nbsp;%+ 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&amp;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) }}
</div>
</div>
</section>
@@ -284,59 +296,82 @@
<th scope="col" class="p-4 font-bold text-brand-navy/70">Whisper local (DIY)</th>
</tr>
</thead>
{# Status SVGs — check (green), x (red), warning (amber). `aria-label` preserves the meaning for AT users. #}
{%- set svg_check = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4 inline-block flex-shrink-0 text-brand-b3" aria-label="Conforme" role="img"><path d="M5 13l4 4L19 7"/></svg>' -%}
{%- set svg_x = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4 inline-block flex-shrink-0 text-red-500" aria-label="Non conforme" role="img"><path d="M6 18L18 6M6 6l12 12"/></svg>' -%}
{%- set svg_warn = '<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 inline-block flex-shrink-0 text-amber-500" aria-label="Partiel" role="img"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>' -%}
<tbody class="divide-y divide-brand-border">
{% for row in [
{
'critere': 'Conforme Loi&nbsp;25 sans transfert hors-Québec',
'dictia': 'Hébergement OVH Beauharnois',
'teams': 'Soumis Cloud Act (US)',
'otter': 'Hébergement US',
'whisper': 'Aucun transfert (local)'
'dictia': {'status': 'check', 'text': 'Hébergement OVH Beauharnois'},
'teams': {'status': 'x', 'text': 'Soumis Cloud Act (US)'},
'otter': {'status': 'x', 'text': 'Hébergement US'},
'whisper':{'status': 'check', 'text': 'Aucun transfert (local)'}
},
{
'critere': 'Souveraineté hors Cloud Act US',
'dictia': 'Aucune exposition',
'teams': 'Microsoft = entité US',
'otter': 'Otter.ai Inc. = US',
'whisper': 'Local'
'dictia': {'status': 'check', 'text': 'Aucune exposition'},
'teams': {'status': 'x', 'text': 'Microsoft = entité US'},
'otter': {'status': 'x', 'text': 'Otter.ai Inc. = US'},
'whisper':{'status': 'check', 'text': 'Local'}
},
{
'critere': 'WhisperX Large-v3 fine-tuné FR-CA',
'dictia': 'FR-CA optimisé',
'teams': 'FR générique (FR-FR)',
'otter': 'Anglais privilégié',
'whisper': 'FR générique de base'
'dictia': {'status': 'check', 'text': 'FR-CA optimisé'},
'teams': {'status': 'warn', 'text': 'FR générique (FR-FR)'},
'otter': {'status': 'x', 'text': 'Anglais privilégié'},
'whisper':{'status': 'warn', 'text': 'FR générique de base'}
},
{
'critere': 'Diarisation jusqu\'à 8 locuteurs (pyannote)',
'dictia': 'Inclus par défaut',
'teams': 'Limité ~6 (Premium)',
'otter': 'Variable selon le forfait',
'whisper': 'Non incluse'
'dictia': {'status': 'check', 'text': 'Inclus par défaut'},
'teams': {'status': 'warn', 'text': 'Limité ~6 (Premium)'},
'otter': {'status': 'warn', 'text': 'Variable selon le forfait'},
'whisper':{'status': 'x', 'text': 'Non incluse'}
},
{
'critere': 'Coût mensuel par utilisateur',
'dictia': '0&nbsp;$ (forfait fixe)',
'teams': '~14&nbsp;$ CAD (Premium)',
'otter': '~20&nbsp;$ US (Business)',
'whisper': '0&nbsp;$ (mais GPU&nbsp;+ DevOps requis)'
'dictia': {'status': None, 'text': '0&nbsp;$ (forfait fixe)'},
'teams': {'status': None, 'text': '~14&nbsp;$ CAD (Premium)'},
'otter': {'status': None, 'text': '~20&nbsp;$ US (Business)'},
'whisper':{'status': None, 'text': '0&nbsp;$ (mais GPU&nbsp;+ DevOps requis)'}
},
{
'critere': 'Audit trail intégré (Loi&nbsp;25 art.&nbsp;3.5)',
'dictia': 'Inclus par défaut',
'teams': 'Via M365 Audit séparé',
'otter': 'Logs basiques seulement',
'whisper': 'À développer soi-même'
'dictia': {'status': 'check', 'text': 'Inclus par défaut'},
'teams': {'status': 'warn', 'text': 'Via M365 Audit séparé'},
'otter': {'status': 'warn', 'text': 'Logs basiques seulement'},
'whisper':{'status': 'x', 'text': 'À développer soi-même'}
}
] %}
{%- set status_svg = {'check': svg_check, 'x': svg_x, 'warn': svg_warn} -%}
<tr class="hover:bg-brand-bg/50 transition-colors">
<th scope="row" class="text-left p-4 font-semibold text-brand-navy/80">{{ row.critere | safe }}</th>
<td class="p-4 text-center font-semibold text-brand-navy">
<span class="inline-block px-2 py-1 rounded-none bg-brand-b3/10 text-brand-navy">{{ row.dictia | safe }}</span>
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-none bg-brand-b3/10 text-brand-navy">
{%- if row.dictia.status -%}{{ status_svg[row.dictia.status] | safe }}{%- endif -%}
<span>{{ row.dictia.text | safe }}</span>
</span>
</td>
<td class="p-4 text-center text-brand-navy/70">
<span class="inline-flex items-center gap-1.5">
{%- if row.teams.status -%}{{ status_svg[row.teams.status] | safe }}{%- endif -%}
<span>{{ row.teams.text | safe }}</span>
</span>
</td>
<td class="p-4 text-center text-brand-navy/70">
<span class="inline-flex items-center gap-1.5">
{%- if row.otter.status -%}{{ status_svg[row.otter.status] | safe }}{%- endif -%}
<span>{{ row.otter.text | safe }}</span>
</span>
</td>
<td class="p-4 text-center text-brand-navy/70">
<span class="inline-flex items-center gap-1.5">
{%- if row.whisper.status -%}{{ status_svg[row.whisper.status] | safe }}{%- endif -%}
<span>{{ row.whisper.text | safe }}</span>
</span>
</td>
<td class="p-4 text-center text-brand-navy/70">{{ row.teams | safe }}</td>
<td class="p-4 text-center text-brand-navy/70">{{ row.otter | safe }}</td>
<td class="p-4 text-center text-brand-navy/70">{{ row.whisper | safe }}</td>
</tr>
{% endfor %}
</tbody>
@@ -366,32 +401,37 @@
</p>
</div>
{# 4 conformity pillars — dark cards with grad-bg icon corners (matches Solution pillars style) #}
{# 4 conformity pillars — dark cards with grad-bg icon corners (matches Solution pillars style).
Icons (heroicons-style outline): map-pin (Québec), scale (Loi 25), building-library (LGGRI), code-bracket (AGPL). #}
{%- set svg_pin = '<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-5 h-5" aria-hidden="true"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>' -%}
{%- set svg_scale = '<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-5 h-5" aria-hidden="true"><path d="M12 3v18"/><path d="M5 7h14"/><path d="M5 7l-2 6a4 4 0 0 0 8 0L9 7"/><path d="M19 7l2 6a4 4 0 0 1-8 0l2-6"/><path d="M8 21h8"/></svg>' -%}
{%- set svg_building = '<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-5 h-5" aria-hidden="true"><path d="M3 21h18"/><path d="M5 21V8l7-4 7 4v13"/><path d="M9 21v-6h6v6"/><path d="M9 11h.01"/><path d="M15 11h.01"/></svg>' -%}
{%- set svg_code = '<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-5 h-5" aria-hidden="true"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/><line x1="14" y1="4" x2="10" y2="20"/></svg>' -%}
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
{% 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&nbsp;27001, SOC&nbsp;2 selon le périmètre). Détails sur demande.'
},
{
'icon': '⚖️',
'icon': svg_scale,
'title': 'Mappé Loi&nbsp;25 (LPRPSP)',
'desc': 'Audit trail art.&nbsp;3.5, EFVP préparée art.&nbsp;3.3, registre des consentements art.&nbsp;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&nbsp;v3 vérifiable',
'desc': 'Architecture entièrement auditable sur <a href="https://gitea.innova-ai.ca/Innova-AI/dictia-public" target="_blank" rel="noopener" class="underline hover:text-white">Gitea public</a>. Aucune boîte noire. Vos auditeurs peuvent examiner chaque ligne.'
}
] %}
<article class="bg-white/[0.05] backdrop-blur-sm p-6 rounded border border-white/[0.08]">
<div class="w-10 h-10 grad-bg rounded-none mb-4 flex items-center justify-center text-lg shadow-cta" aria-hidden="true">{{ card.icon }}</div>
<div class="w-10 h-10 grad-bg rounded-none mb-4 flex items-center justify-center text-white shadow-cta" aria-hidden="true">{{ card.icon | safe }}</div>
<h3 class="text-lg font-bold mb-2 text-white">{{ card.title | safe }}</h3>
<p class="text-sm text-white/80 leading-relaxed">{{ card.desc | safe }}</p>
</article>
@@ -423,10 +463,13 @@
{% for t in testimonials %}
<article class="bg-white p-6 rounded border border-brand-border flex flex-col items-center text-center"
aria-label="Témoignage {{ t.placeholder_label }} à venir">
<div class="w-16 h-16 rounded-full grad-bg flex items-center justify-center mb-4 shadow-cta" aria-hidden="true">
{%- if t.persona == 'avocat' -%}<span class="text-2xl">⚖️</span>
{%- elif t.persona == 'cpa' -%}<span class="text-2xl">📊</span>
{%- elif t.persona == 'municipal' -%}<span class="text-2xl">🏛️</span>
<div class="w-16 h-16 rounded-full grad-bg flex items-center justify-center mb-4 shadow-cta text-white" aria-hidden="true">
{%- if t.persona == 'avocat' -%}
<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-7 h-7"><path d="M12 3v18"/><path d="M5 7h14"/><path d="M5 7l-2 6a4 4 0 0 0 8 0L9 7"/><path d="M19 7l2 6a4 4 0 0 1-8 0l2-6"/><path d="M8 21h8"/></svg>
{%- elif t.persona == 'cpa' -%}
<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-7 h-7"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/><line x1="3" y1="20" x2="21" y2="20"/></svg>
{%- elif t.persona == 'municipal' -%}
<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-7 h-7"><path d="M3 21h18"/><path d="M5 21V8l7-4 7 4v13"/><path d="M9 21v-6h6v6"/><path d="M9 11h.01"/><path d="M15 11h.01"/></svg>
{%- endif -%}
</div>
<p class="text-sm font-bold text-brand-navy mb-1">{{ t.placeholder_label | safe }}</p>
@@ -520,7 +563,7 @@
<div class="flex flex-col sm:flex-row gap-4 justify-center">
{% 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='<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="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>') }}
{{ button('Voir les forfaits', href='#tarifs', variant='ghost', size='lg') }}
</div>