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

@@ -8,7 +8,9 @@
{# ===== HERO ===== #} {# ===== HERO ===== #}
<section class="bg-brand-navy text-white py-20" aria-labelledby="page-title"> <section class="bg-brand-navy text-white py-20" aria-labelledby="page-title">
<div class="max-w-[820px] mx-auto px-6 text-center"> <div class="max-w-[820px] mx-auto px-6 text-center">
<div class="w-20 h-20 bg-white/[0.06] border border-white/[0.12] rounded-full mx-auto mb-6 flex items-center justify-center text-3xl" aria-hidden="true"></div> <div class="w-20 h-20 bg-white/[0.06] border border-white/[0.12] rounded-full mx-auto mb-6 flex items-center justify-center text-white/80" aria-hidden="true">
<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-9 h-9"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>
</div>
<p class="eyebrow grad-text mb-4">PAIEMENT ANNULÉ</p> <p class="eyebrow grad-text mb-4">PAIEMENT ANNULÉ</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">
Aucun problème — <span class="grad-text">aucun montant prélevé</span>. Aucun problème — <span class="grad-text">aucun montant prélevé</span>.

View File

@@ -8,7 +8,9 @@
{# ===== HERO ===== #} {# ===== HERO ===== #}
<section class="bg-brand-navy text-white py-20" aria-labelledby="page-title"> <section class="bg-brand-navy text-white py-20" aria-labelledby="page-title">
<div class="max-w-[820px] mx-auto px-6 text-center"> <div class="max-w-[820px] mx-auto px-6 text-center">
<div class="w-20 h-20 grad-bg rounded-full mx-auto mb-6 flex items-center justify-center text-4xl shadow-cta" aria-hidden="true"></div> <div class="w-20 h-20 grad-bg rounded-full mx-auto mb-6 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="3" stroke-linecap="round" stroke-linejoin="round" class="w-10 h-10"><path d="M5 13l4 4L19 7"/></svg>
</div>
<p class="eyebrow grad-text mb-4">PAIEMENT CONFIRMÉ</p> <p class="eyebrow grad-text mb-4">PAIEMENT CONFIRMÉ</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">
Merci&nbsp;! Votre <span class="grad-text">paiement est confirmé</span>. Merci&nbsp;! Votre <span class="grad-text">paiement est confirmé</span>.

View File

@@ -269,8 +269,9 @@
<a href="{{ url_for('legal.legal_page', page=prev_page) }}" <a href="{{ url_for('legal.legal_page', page=prev_page) }}"
rel="prev" rel="prev"
class="block p-4 bg-brand-bg/60 border border-brand-border rounded hover:border-brand-b1 hover:bg-white transition focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2"> class="block p-4 bg-brand-bg/60 border border-brand-border rounded hover:border-brand-b1 hover:bg-white transition focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2">
<span class="block text-xs uppercase tracking-wider text-brand-navy/60 mb-1"> <span class="block text-xs uppercase tracking-wider text-brand-navy/60 mb-1 inline-flex items-center gap-1.5">
<span aria-hidden="true"></span> Précédent <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-3.5 h-3.5" aria-hidden="true"><path d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
Précédent
</span> </span>
<span class="block text-sm font-semibold text-brand-navy">{{ prev_title }}</span> <span class="block text-sm font-semibold text-brand-navy">{{ prev_title }}</span>
</a> </a>
@@ -282,8 +283,9 @@
<a href="{{ url_for('legal.legal_page', page=next_page) }}" <a href="{{ url_for('legal.legal_page', page=next_page) }}"
rel="next" rel="next"
class="block p-4 bg-brand-bg/60 border border-brand-border rounded hover:border-brand-b1 hover:bg-white transition focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2 sm:text-right"> class="block p-4 bg-brand-bg/60 border border-brand-border rounded hover:border-brand-b1 hover:bg-white transition focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2 sm:text-right">
<span class="block text-xs uppercase tracking-wider text-brand-navy/60 mb-1"> <span class="block text-xs uppercase tracking-wider text-brand-navy/60 mb-1 inline-flex items-center gap-1.5 sm:justify-end">
Suivant <span aria-hidden="true"></span> Suivant
<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-3.5 h-3.5" aria-hidden="true"><path d="M14 5l7 7m0 0l-7 7m7-7H3"/></svg>
</span> </span>
<span class="block text-sm font-semibold text-brand-navy">{{ next_title }}</span> <span class="block text-sm font-semibold text-brand-navy">{{ next_title }}</span>
</a> </a>
@@ -292,8 +294,8 @@
{% endif %} {% endif %}
<footer class="mt-8 pt-6 border-t border-brand-border text-sm text-brand-navy/70"> <footer class="mt-8 pt-6 border-t border-brand-border text-sm text-brand-navy/70">
<p> <p class="inline-flex items-center gap-1.5">
<span aria-hidden="true"></span> <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="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
<a href="{{ url_for('legal.legal_index') }}" class="grad-text font-semibold">Index des documents légaux</a> <a href="{{ url_for('legal.legal_index') }}" class="grad-text font-semibold">Index des documents légaux</a>
</p> </p>
</footer> </footer>

View File

@@ -79,12 +79,12 @@
</div> </div>
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<h2 class="text-base md:text-lg font-bold text-brand-navy mb-1.5 group-hover:grad-text transition-colors"> <h2 class="text-base md:text-lg font-bold text-brand-navy mb-1.5 group-hover:grad-text transition-colors">
{{ page.title }}{% if page.external %}<span class="ml-1.5 text-brand-b1" aria-hidden="true"></span>{% endif %} {{ page.title }}{% if page.external %}<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="inline-block w-4 h-4 ml-1.5 text-brand-b1 align-text-top" aria-hidden="true"><path d="M7 17l9.2-9.2M17 17V8h-9"/></svg>{% endif %}
</h2> </h2>
<p class="text-sm text-brand-navy/70 leading-relaxed">{{ page.description }}</p> <p class="text-sm text-brand-navy/70 leading-relaxed">{{ page.description }}</p>
{% if page.external %} {% if page.external %}
<p class="mt-2 text-xs text-brand-navy/50 font-medium"> <p class="mt-2 inline-flex items-center gap-1.5 text-xs text-brand-navy/50 font-medium">
<span aria-hidden="true"></span> <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-3.5 h-3.5" aria-hidden="true"><path d="M14 5l7 7m0 0l-7 7m7-7H3"/></svg>
<span>{{ page.url }}</span> <span>{{ page.url }}</span>
<span class="sr-only">(s'ouvre dans un nouvel onglet)</span> <span class="sr-only">(s'ouvre dans un nouvel onglet)</span>
</p> </p>

View File

@@ -1,12 +1,15 @@
{# Reusable bento card macro. FlexiHub style: dark navy2 surface, decorative watermark number, gradient icon corner. {# 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, `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). #} so dynamic `col-span-{{ span }}` would produce dead classes — the lookup keeps the utilities discoverable).
{% macro bento_card(number, title, description, icon='✦', span='1') %} `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 span_classes = {'1': 'col-span-1', '2': 'sm:col-span-2', '3': 'sm:col-span-2 md:col-span-3'} -%}
{%- set default_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-5 h-5 text-white" aria-hidden="true"><path d="M12 3l1.8 5.4L19 10l-5.2 1.6L12 17l-1.8-5.4L5 10l5.2-1.6z"/></svg>' -%}
<div class="relative bg-brand-navy2 p-6 rounded overflow-hidden border border-white/[0.045] {{ span_classes.get(span, 'col-span-1') }}"> <div class="relative bg-brand-navy2 p-6 rounded overflow-hidden border border-white/[0.045] {{ span_classes.get(span, 'col-span-1') }}">
<div class="absolute top-2 right-4 text-[80px] font-black text-white/[0.04]" aria-hidden="true">{{ number }}</div> <div class="absolute top-2 right-4 text-[80px] font-black text-white/[0.04]" aria-hidden="true">{{ number }}</div>
<div class="relative"> <div class="relative">
<div class="w-10 h-10 grad-bg rounded-none mb-4 flex items-center justify-center text-lg">{{ icon }}</div> <div class="w-10 h-10 grad-bg rounded-none mb-4 flex items-center justify-center text-white" aria-hidden="true">{{ (icon or default_icon) | safe }}</div>
<h3 class="text-lg font-bold mb-2 text-white">{{ title | safe }}</h3> <h3 class="text-lg font-bold mb-2 text-white">{{ title | safe }}</h3>
<p class="text-sm text-white/70">{{ description | safe }}</p> <p class="text-sm text-white/70">{{ description | safe }}</p>
</div> </div>

View File

@@ -16,7 +16,7 @@
(verified: "DictIA 8", "DictIA 16", "DictIA Cloud" are all entity-free). #} (verified: "DictIA 8", "DictIA 16", "DictIA Cloud" are all entity-free). #}
{%- macro pricing_card(slug, name, price_setup, price_monthly, target, features, recommended=False, cta_url='/checkout') -%} {%- macro pricing_card(slug, name, price_setup, price_monthly, target, features, recommended=False, cta_url='/checkout') -%}
<div class="relative {% if recommended %}grad-bg p-[1.5px] rounded{% endif %}"> <div class="relative {% if recommended %}grad-bg p-[1.5px] rounded{% endif %}">
{% if recommended %}<span class="absolute -top-3 left-1/2 -translate-x-1/2 grad-bg text-white text-xs font-bold px-3 py-1 rounded-full shadow-cta">RECOMMANDÉ</span>{% endif %} {% if recommended %}<span class="absolute -top-3 left-1/2 -translate-x-1/2 grad-bg text-white text-xs font-bold px-3 py-1 rounded-full shadow-cta inline-flex items-center gap-1.5"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-3 h-3" aria-hidden="true"><path d="M12 2l2.9 6.9L22 10l-5.5 4.8L18 22l-6-3.6L6 22l1.5-7.2L2 10l7.1-1.1z"/></svg>RECOMMANDÉ</span>{% endif %}
<div class="bg-white p-8 rounded border border-brand-border h-full flex flex-col"> <div class="bg-white p-8 rounded border border-brand-border h-full flex flex-col">
<div class="mb-6"> <div class="mb-6">
<h3 class="text-xl font-black mb-1 text-brand-navy">{{ name | safe }}</h3> <h3 class="text-xl font-black mb-1 text-brand-navy">{{ name | safe }}</h3>
@@ -29,7 +29,7 @@
<ul class="space-y-3 mb-8 flex-grow"> <ul class="space-y-3 mb-8 flex-grow">
{% for f in features %} {% for f in features %}
<li class="flex items-start gap-2 text-sm text-brand-navy/80"> <li class="flex items-start gap-2 text-sm text-brand-navy/80">
<span class="grad-text font-black flex-shrink-0" aria-hidden="true"></span> <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 mt-0.5 flex-shrink-0 text-brand-b3" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>
<span>{{ f | safe }}</span> <span>{{ f | safe }}</span>
</li> </li>
{% endfor %} {% endfor %}

View File

@@ -21,8 +21,6 @@
<ul class="space-y-2 text-sm text-white/70"> <ul class="space-y-2 text-sm text-white/70">
<li><a href="/fonctionnalites" class="hover:text-white">Fonctionnalités</a></li> <li><a href="/fonctionnalites" class="hover:text-white">Fonctionnalités</a></li>
<li><a href="/tarifs" class="hover:text-white">Tarifs</a></li> <li><a href="/tarifs" class="hover:text-white">Tarifs</a></li>
<li><a href="/conformite" class="hover:text-white">Conformité</a></li>
<li><a href="/blog" class="hover:text-white">Blog</a></li>
</ul> </ul>
</nav> </nav>
@@ -30,12 +28,14 @@
<nav aria-label="Légal"> <nav aria-label="Légal">
<p class="eyebrow text-white/70 mb-4">Légal</p> <p class="eyebrow text-white/70 mb-4">Légal</p>
<ul class="space-y-2 text-sm text-white/70"> <ul class="space-y-2 text-sm text-white/70">
<li><a href="/conformite" class="hover:text-white">Conformité</a></li>
<li><a href="/legal/conditions" class="hover:text-white">Conditions d'utilisation</a></li> <li><a href="/legal/conditions" class="hover:text-white">Conditions d'utilisation</a></li>
<li><a href="/legal/confidentialite" class="hover:text-white">Confidentialité (Loi&nbsp;25)</a></li> <li><a href="/legal/confidentialite" class="hover:text-white">Confidentialité (Loi&nbsp;25)</a></li>
<li><a href="/legal/cookies" class="hover:text-white">Cookies</a></li> <li><a href="/legal/cookies" class="hover:text-white">Cookies</a></li>
<li><a href="/legal/remboursement" class="hover:text-white">Remboursement</a></li> <li><a href="/legal/remboursement" class="hover:text-white">Remboursement</a></li>
<li><a href="/legal/accessibilite" class="hover:text-white">Accessibilité</a></li> <li><a href="/legal/accessibilite" class="hover:text-white">Accessibilité</a></li>
<li><a href="/legal/mentions" class="hover:text-white">Mentions légales</a></li> <li><a href="/legal/mentions" class="hover:text-white">Mentions légales</a></li>
<li><a href="https://gitea.dictia.ca/Innova-AI/dictia-public" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 hover:text-white">Code source AGPL<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-3.5 h-3.5" aria-hidden="true"><path d="M7 17l9.2-9.2M17 17V8h-9"/></svg><span class="sr-only">(s'ouvre dans un nouvel onglet)</span></a></li>
</ul> </ul>
</nav> </nav>
@@ -46,7 +46,6 @@
<li><a href="/login" class="hover:text-white">Connexion</a></li> <li><a href="/login" class="hover:text-white">Connexion</a></li>
<li><a href="/signup" class="hover:text-white">Créer un compte</a></li> <li><a href="/signup" class="hover:text-white">Créer un compte</a></li>
<li><a href="/contact" class="hover:text-white">Contact</a></li> <li><a href="/contact" class="hover:text-white">Contact</a></li>
<li><a href="https://gitea.dictia.ca/Innova-AI/dictia-public" target="_blank" rel="noopener" class="hover:text-white">Code source AGPL&nbsp;<span aria-hidden="true"></span><span class="sr-only">(s'ouvre dans un nouvel onglet)</span></a></li>
</ul> </ul>
</nav> </nav>
</div> </div>

View File

@@ -40,20 +40,21 @@
<!-- Glassmorphism header (FlexiHub style: 62px, navy/.97 + backdrop-blur-xl + 0.045 border) --> <!-- Glassmorphism header (FlexiHub style: 62px, navy/.97 + backdrop-blur-xl + 0.045 border) -->
<header class="fixed top-0 inset-x-0 z-50 h-[62px] bg-brand-navy/[0.97] backdrop-blur-xl border-b border-white/[0.045]"> <header class="fixed top-0 inset-x-0 z-50 h-[62px] bg-brand-navy/[0.97] backdrop-blur-xl border-b border-white/[0.045]">
<div class="max-w-[1200px] mx-auto h-full px-6 flex items-center justify-between"> <div class="max-w-[1200px] mx-auto h-full px-6 flex items-center justify-between">
<a href="/" class="font-black text-xl tracking-tight grad-text" aria-label="DictIA — accueil">DictIA</a> <a href="/" class="flex flex-col leading-none" aria-label="DictIA — Transcription, accueil">
<span class="font-black text-xl tracking-tight grad-text">DictIA</span>
<span class="text-[10px] uppercase tracking-[0.2em] text-white font-medium mt-0.5">Transcription</span>
</a>
<nav class="hidden md:flex gap-8 text-sm font-medium text-white/80" aria-label="Navigation principale"> <nav class="hidden md:flex gap-8 text-sm font-medium text-white/80" aria-label="Navigation principale">
<a href="/fonctionnalites" class="hover:text-white transition">Fonctionnalités</a> <a href="/fonctionnalites" class="hover:text-white transition">Fonctionnalités</a>
<a href="/conformite" class="hover:text-white transition">Conformité</a>
<a href="/tarifs" class="hover:text-white transition">Tarifs</a> <a href="/tarifs" class="hover:text-white transition">Tarifs</a>
<a href="/blog" class="hover:text-white transition">Blog</a>
<a href="/contact" class="hover:text-white transition">Contact</a> <a href="/contact" class="hover:text-white transition">Contact</a>
</nav> </nav>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<a href="/login" class="text-sm font-medium text-white/80 hover:text-white">Connexion</a> <a href="/login" class="text-sm font-medium text-white/80 hover:text-white">Connexion</a>
{% from 'macros/button.html' import button %} {% from 'macros/button.html' import button %}
{{ button('Démarrer', href='/signup', variant='primary', size='sm') }} {{ button('Démarrer', href='/signup', variant='primary', size='sm', 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> </div>
</div> </div>
</header> </header>

View File

@@ -29,31 +29,36 @@
Pourquoi la conformité est <span class="grad-text">structurelle</span>, pas optionnelle. Pourquoi la conformité est <span class="grad-text">structurelle</span>, pas optionnelle.
</h2> </h2>
</div> </div>
{# Icons (heroicons-style outline) — pin (QC), scale (Loi 25), building (Cadre IA), code (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"> <div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
{% for card in [ {% for card in [
{ {
'icon': '🍁', 'icon': svg_pin,
'title': 'Stockage OVH Beauharnois&nbsp;(QC)', 'title': 'Stockage OVH Beauharnois&nbsp;(QC)',
'desc': 'Stockage persistant chez OVHcloud Canada à Beauharnois, Québec. Traitement GPU temporaire sur GCP Toronto (Ontario)&nbsp;: RAM uniquement, durée maximale 5&nbsp;minutes par session, zéro persistance — encadré par EFVP signée. Données médicales et biométriques jamais hors du Canada.' 'desc': 'Stockage persistant chez OVHcloud Canada à Beauharnois, Québec. Traitement GPU temporaire sur GCP Toronto (Ontario)&nbsp;: RAM uniquement, durée maximale 5&nbsp;minutes par session, zéro persistance — encadré par EFVP signée. Données médicales et biométriques jamais hors du Canada.'
}, },
{ {
'icon': '⚖️', 'icon': svg_scale,
'title': 'Mappé Loi&nbsp;25 (LPRPSP)', 'title': 'Mappé Loi&nbsp;25 (LPRPSP)',
'desc': 'Audit trail art.&nbsp;3.5, EFVP signées art.&nbsp;3.3 et 17 (GCP, HubSpot), registre des consentements art.&nbsp;14, déclaration CAI biométrie (formulaire K1) préparée. Modèles disponibles sur demande.' 'desc': 'Audit trail art.&nbsp;3.5, EFVP signées art.&nbsp;3.3 et 17 (GCP, HubSpot), registre des consentements art.&nbsp;14, déclaration CAI biométrie (formulaire K1) préparée. Modèles disponibles sur demande.'
}, },
{ {
'icon': '🏛️', 'icon': svg_building,
'title': 'Compatible Cadre IA secteur public', '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.' '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', 'title': 'Code source AGPL&nbsp;v3 vérifiable',
'desc': 'Fork du projet open source Speakr — architecture entièrement auditable sur <a href="https://gitea.dictia.ca/Innova-AI/dictia-public" target="_blank" rel="noopener" class="underline hover:text-brand-navy">Gitea public</a>. Aucune boîte noire. Vos auditeurs peuvent examiner chaque ligne.' 'desc': 'Fork du projet open source Speakr — architecture entièrement auditable sur <a href="https://gitea.dictia.ca/Innova-AI/dictia-public" target="_blank" rel="noopener" class="underline hover:text-brand-navy">Gitea public</a>. Aucune boîte noire. Vos auditeurs peuvent examiner chaque ligne.'
} }
] %} ] %}
<article class="bg-brand-bg p-6 rounded border border-brand-border"> <article class="bg-brand-bg p-6 rounded border border-brand-border">
<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-brand-navy">{{ card.title | safe }}</h3> <h3 class="text-lg font-bold mb-2 text-brand-navy">{{ card.title | safe }}</h3>
<p class="text-sm text-brand-navy/80 leading-relaxed">{{ card.desc | safe }}</p> <p class="text-sm text-brand-navy/80 leading-relaxed">{{ card.desc | safe }}</p>
</article> </article>
@@ -121,7 +126,7 @@
</p> </p>
<div class="flex flex-col sm:flex-row gap-4 justify-center"> <div class="flex flex-col sm:flex-row gap-4 justify-center">
{% from 'macros/button.html' import button %} {% from 'macros/button.html' import button %}
{{ button('Code source sur Gitea', href='https://gitea.dictia.ca/Innova-AI/dictia-public', variant='primary', size='lg', icon='', target='_blank', rel='noopener') }} {{ button('Code source sur Gitea', href='https://gitea.dictia.ca/Innova-AI/dictia-public', 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="M7 17l9.2-9.2M17 17V8h-9"/></svg>', target='_blank', rel='noopener') }}
{{ button('Comprendre AGPL v3', href='https://www.gnu.org/licenses/agpl-3.0.fr.html', variant='ghost', size='lg', target='_blank', rel='noopener') }} {{ button('Comprendre AGPL v3', href='https://www.gnu.org/licenses/agpl-3.0.fr.html', variant='ghost', size='lg', target='_blank', rel='noopener') }}
</div> </div>
</div> </div>
@@ -138,7 +143,7 @@
</p> </p>
<div class="flex flex-col sm:flex-row gap-4 justify-center"> <div class="flex flex-col sm:flex-row gap-4 justify-center">
{% from 'macros/button.html' import button %} {% 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='<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='secondary', size='lg') }} {{ button('Voir les forfaits', href='/tarifs', variant='secondary', size='lg') }}
</div> </div>
</div> </div>

View File

@@ -13,7 +13,7 @@
Parlons <span class="grad-text">de votre projet</span>. Parlons <span class="grad-text">de votre projet</span>.
</h1> </h1>
<p class="text-lg text-white/80"> <p class="text-lg text-white/80">
Réponse sous 2&nbsp;jours ouvrables. Pour les urgences techniques des clients existants, voyez la console DictIA → Support. Réponse sous 2&nbsp;jours ouvrables. Pour les urgences techniques des clients existants, voyez la section Support de la console DictIA.
</p> </p>
</div> </div>
</section> </section>
@@ -26,7 +26,9 @@
{# Email card #} {# Email card #}
<article class="bg-white p-8 rounded border border-brand-border flex flex-col"> <article class="bg-white p-8 rounded border border-brand-border flex flex-col">
<div class="w-12 h-12 grad-bg rounded-none mb-4 flex items-center justify-center text-2xl shadow-cta" aria-hidden="true">✉️</div> <div class="w-12 h-12 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" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6"><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>
</div>
<h3 class="text-lg font-bold mb-2 text-brand-navy">Courriel</h3> <h3 class="text-lg font-bold mb-2 text-brand-navy">Courriel</h3>
<p class="text-sm text-brand-navy/80 mb-4 leading-relaxed flex-grow"> <p class="text-sm text-brand-navy/80 mb-4 leading-relaxed flex-grow">
Privilégiez le courriel pour&nbsp;: pré-inscription, devis, démonstration, dossier de conformité, partenariats. Privilégiez le courriel pour&nbsp;: pré-inscription, devis, démonstration, dossier de conformité, partenariats.
@@ -36,7 +38,9 @@
{# Phone card #} {# Phone card #}
<article class="bg-white p-8 rounded border border-brand-border flex flex-col"> <article class="bg-white p-8 rounded border border-brand-border flex flex-col">
<div class="w-12 h-12 grad-bg rounded-none mb-4 flex items-center justify-center text-2xl shadow-cta" aria-hidden="true">☎️</div> <div class="w-12 h-12 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" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
</div>
<h3 class="text-lg font-bold mb-2 text-brand-navy">Téléphone</h3> <h3 class="text-lg font-bold mb-2 text-brand-navy">Téléphone</h3>
<p class="text-sm text-brand-navy/80 mb-4 leading-relaxed flex-grow"> <p class="text-sm text-brand-navy/80 mb-4 leading-relaxed flex-grow">
Du lundi au vendredi, 9&nbsp;h à 17&nbsp;h (heure de l'Est). Laissez un message en dehors de ces heures. Du lundi au vendredi, 9&nbsp;h à 17&nbsp;h (heure de l'Est). Laissez un message en dehors de ces heures.
@@ -46,7 +50,9 @@
{# Mailing address card #} {# Mailing address card #}
<article class="bg-white p-8 rounded border border-brand-border flex flex-col"> <article class="bg-white p-8 rounded border border-brand-border flex flex-col">
<div class="w-12 h-12 grad-bg rounded-none mb-4 flex items-center justify-center text-2xl shadow-cta" aria-hidden="true">📬</div> <div class="w-12 h-12 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" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6"><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>
</div>
<h3 class="text-lg font-bold mb-2 text-brand-navy">Bureau</h3> <h3 class="text-lg font-bold mb-2 text-brand-navy">Bureau</h3>
<p class="text-sm text-brand-navy/80 mb-4 leading-relaxed flex-grow"> <p class="text-sm text-brand-navy/80 mb-4 leading-relaxed flex-grow">
Sur rendez-vous uniquement. Visites en personne pour démonstrations on-premise et déploiements DictIA 16 corporatifs. Sur rendez-vous uniquement. Visites en personne pour démonstrations on-premise et déploiements DictIA 16 corporatifs.
@@ -73,18 +79,25 @@
</p> </p>
</div> </div>
{# Shortcut icons (heroicons-style outline). Each is a self-contained inline SVG passed via | safe in the loop. #}
{%- set svg_target = '<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"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>' -%}
{%- set svg_office = '<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="4" y="2" width="16" height="20" rx="2"/><line x1="9" y1="6" x2="9" y2="6"/><line x1="15" y1="6" x2="15" y2="6"/><line x1="9" y1="10" x2="9" y2="10"/><line x1="15" y1="10" x2="15" y2="10"/><line x1="9" y1="14" x2="9" y2="14"/><line x1="15" y1="14" x2="15" y2="14"/><path d="M10 22v-4h4v4"/></svg>' -%}
{%- set svg_play = '<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="2" y="5" width="20" height="14" rx="2"/><polygon points="10 9 16 12 10 15 10 9" fill="currentColor"/></svg>' -%}
{%- set svg_scale_sm = '<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_handshake = '<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="M16 12l-4-4-4 4"/><path d="M3 13l5 5 4-4 4 4 5-5"/><path d="M3 9l9 9 9-9"/></svg>' -%}
{%- set svg_news = '<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="M4 4h13a2 2 0 0 1 2 2v14H6a2 2 0 0 1-2-2z"/><path d="M19 8h2a1 1 0 0 1 1 1v9a2 2 0 0 1-2 2"/><line x1="8" y1="9" x2="15" y2="9"/><line x1="8" y1="13" x2="15" y2="13"/><line x1="8" y1="17" x2="12" y2="17"/></svg>' -%}
<div class="grid sm:grid-cols-2 gap-3"> <div class="grid sm:grid-cols-2 gap-3">
{% for shortcut in [ {% for shortcut in [
{'label': 'Pré-inscription DictIA', 'subject': 'Pr%C3%A9-inscription%20DictIA', 'icon': '🎯'}, {'label': 'Pré-inscription DictIA', 'subject': 'Pr%C3%A9-inscription%20DictIA', 'icon': svg_target},
{'label': 'Devis multi-sites', 'subject': 'Devis%20multi-sites', 'icon': '🏢'}, {'label': 'Devis multi-sites', 'subject': 'Devis%20multi-sites', 'icon': svg_office},
{'label': 'Demande de démonstration', 'subject': 'Demande%20de%20d%C3%A9monstration', 'icon': '📺'}, {'label': 'Demande de démonstration', 'subject': 'Demande%20de%20d%C3%A9monstration', 'icon': svg_play},
{'label': 'Dossier conformité Loi 25', 'subject': 'Dossier%20conformit%C3%A9%20Loi%2025', 'icon': '⚖️'}, {'label': 'Dossier conformité Loi 25', 'subject': 'Dossier%20conformit%C3%A9%20Loi%2025', 'icon': svg_scale_sm},
{'label': 'Partenariat / intégration', 'subject': 'Partenariat%20/%20int%C3%A9gration', 'icon': '🤝'}, {'label': 'Partenariat / intégration', 'subject': 'Partenariat%20/%20int%C3%A9gration', 'icon': svg_handshake},
{'label': 'Question média / presse', 'subject': 'Question%20m%C3%A9dia', 'icon': '📰'} {'label': 'Question média / presse', 'subject': 'Question%20m%C3%A9dia', 'icon': svg_news}
] %} ] %}
<a href="mailto:info@dictia.ca?subject={{ shortcut.subject }}" <a href="mailto:info@dictia.ca?subject={{ shortcut.subject }}"
class="flex items-center gap-3 bg-brand-bg p-4 rounded border border-brand-border hover:bg-white hover:border-brand-b1/30 transition-colors focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2"> class="flex items-center gap-3 bg-brand-bg p-4 rounded border border-brand-border hover:bg-white hover:border-brand-b1/30 transition-colors focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2">
<span class="text-2xl flex-shrink-0" aria-hidden="true">{{ shortcut.icon }}</span> <span class="grad-text flex-shrink-0" aria-hidden="true">{{ shortcut.icon | safe }}</span>
<span class="text-sm font-semibold text-brand-navy">{{ shortcut.label | safe }}</span> <span class="text-sm font-semibold text-brand-navy">{{ shortcut.label | safe }}</span>
</a> </a>
{% endfor %} {% endfor %}

View File

@@ -23,15 +23,22 @@
<div class="max-w-[1060px] mx-auto px-6"> <div class="max-w-[1060px] mx-auto px-6">
<h2 id="features-title" class="sr-only">Six fonctionnalités principales</h2> <h2 id="features-title" class="sr-only">Six fonctionnalités principales</h2>
{# NOTE: bento card content is duplicated between landing.html and fonctionnalites.html. {# 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 %} {% 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"> <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('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.', '👥') }} {{ 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.', '📝') }} {{ 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.', '💬') }} {{ 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.', '📄') }} {{ 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.', '🔌') }} {{ bento_card('06', 'Intégrations', 'Word, Outlook, Teams, Notion, Obsidian, Zapier, Make, n8n.', icon_plug) }}
</div> </div>
</div> </div>
</section> </section>
@@ -137,7 +144,7 @@
</p> </p>
<div class="flex flex-col sm:flex-row gap-4 justify-center"> <div class="flex flex-col sm:flex-row gap-4 justify-center">
{% from 'macros/button.html' import button %} {% 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 tarifs', href='/tarifs', variant='secondary', size='lg') }} {{ button('Voir les tarifs', href='/tarifs', variant='secondary', size='lg') }}
</div> </div>
</div> </div>

View File

@@ -20,7 +20,7 @@
{# Subtle grid overlay (FlexiHub signature) #} {# Subtle grid overlay (FlexiHub signature) #}
<div class="absolute inset-0" <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> 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" <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> style="background: linear-gradient(90deg, transparent, rgba(0,98,255,0.3), rgba(0,189,216,0.2), transparent);"></div>
</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;"> <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 %} {% from 'macros/button.html' import button %}
{{ button('Réserver une démo', href='/contact', variant='primary', size='lg') }} {{ 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> </div>
{# Social proof microcopy — defensible: refers to pre-launch waitlist + factual ordres pros count #} {# 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 #} {# 3 problem cards on white surface — Cloud Act, Loi 25, Sanctions #}
<div class="grid md:grid-cols-3 gap-6 mt-12"> <div class="grid md:grid-cols-3 gap-6 mt-12">
{% for card in [ {% 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.', '⚖️'), ('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.', '🛡️'), '<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>'),
('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.', '⚠️') ('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"> <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> <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-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> <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.') ('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]"> <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> <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> <p class="text-sm text-white/70 leading-relaxed">{{ pillar[1] | safe }}</p>
</article> </article>
@@ -196,15 +201,22 @@
</div> </div>
{# NOTE: bento card content is duplicated between landing.html and fonctionnalites.html. {# 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 %} {% 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"> <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('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.', '👥') }} {{ 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.', '📝') }} {{ 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.', '💬') }} {{ 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.', '📄') }} {{ 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.', '🔌') }} {{ bento_card('06', 'Intégrations', 'Word, Outlook, Teams, Notion, Obsidian, Zapier, Make, n8n.', icon_plug) }}
</div> </div>
</div> </div>
</section> </section>
@@ -284,59 +296,82 @@
<th scope="col" class="p-4 font-bold text-brand-navy/70">Whisper local (DIY)</th> <th scope="col" class="p-4 font-bold text-brand-navy/70">Whisper local (DIY)</th>
</tr> </tr>
</thead> </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"> <tbody class="divide-y divide-brand-border">
{% for row in [ {% for row in [
{ {
'critere': 'Conforme Loi&nbsp;25 sans transfert hors-Québec', 'critere': 'Conforme Loi&nbsp;25 sans transfert hors-Québec',
'dictia': 'Hébergement OVH Beauharnois', 'dictia': {'status': 'check', 'text': 'Hébergement OVH Beauharnois'},
'teams': 'Soumis Cloud Act (US)', 'teams': {'status': 'x', 'text': 'Soumis Cloud Act (US)'},
'otter': 'Hébergement US', 'otter': {'status': 'x', 'text': 'Hébergement US'},
'whisper': 'Aucun transfert (local)' 'whisper':{'status': 'check', 'text': 'Aucun transfert (local)'}
}, },
{ {
'critere': 'Souveraineté hors Cloud Act US', 'critere': 'Souveraineté hors Cloud Act US',
'dictia': 'Aucune exposition', 'dictia': {'status': 'check', 'text': 'Aucune exposition'},
'teams': 'Microsoft = entité US', 'teams': {'status': 'x', 'text': 'Microsoft = entité US'},
'otter': 'Otter.ai Inc. = US', 'otter': {'status': 'x', 'text': 'Otter.ai Inc. = US'},
'whisper': 'Local' 'whisper':{'status': 'check', 'text': 'Local'}
}, },
{ {
'critere': 'WhisperX Large-v3 fine-tuné FR-CA', 'critere': 'WhisperX Large-v3 fine-tuné FR-CA',
'dictia': 'FR-CA optimisé', 'dictia': {'status': 'check', 'text': 'FR-CA optimisé'},
'teams': 'FR générique (FR-FR)', 'teams': {'status': 'warn', 'text': 'FR générique (FR-FR)'},
'otter': 'Anglais privilégié', 'otter': {'status': 'x', 'text': 'Anglais privilégié'},
'whisper': 'FR générique de base' 'whisper':{'status': 'warn', 'text': 'FR générique de base'}
}, },
{ {
'critere': 'Diarisation jusqu\'à 8 locuteurs (pyannote)', 'critere': 'Diarisation jusqu\'à 8 locuteurs (pyannote)',
'dictia': 'Inclus par défaut', 'dictia': {'status': 'check', 'text': 'Inclus par défaut'},
'teams': 'Limité ~6 (Premium)', 'teams': {'status': 'warn', 'text': 'Limité ~6 (Premium)'},
'otter': 'Variable selon le forfait', 'otter': {'status': 'warn', 'text': 'Variable selon le forfait'},
'whisper': 'Non incluse' 'whisper':{'status': 'x', 'text': 'Non incluse'}
}, },
{ {
'critere': 'Coût mensuel par utilisateur', 'critere': 'Coût mensuel par utilisateur',
'dictia': '0&nbsp;$ (forfait fixe)', 'dictia': {'status': None, 'text': '0&nbsp;$ (forfait fixe)'},
'teams': '~14&nbsp;$ CAD (Premium)', 'teams': {'status': None, 'text': '~14&nbsp;$ CAD (Premium)'},
'otter': '~20&nbsp;$ US (Business)', 'otter': {'status': None, 'text': '~20&nbsp;$ US (Business)'},
'whisper': '0&nbsp;$ (mais GPU&nbsp;+ DevOps requis)' 'whisper':{'status': None, 'text': '0&nbsp;$ (mais GPU&nbsp;+ DevOps requis)'}
}, },
{ {
'critere': 'Audit trail intégré (Loi&nbsp;25 art.&nbsp;3.5)', 'critere': 'Audit trail intégré (Loi&nbsp;25 art.&nbsp;3.5)',
'dictia': 'Inclus par défaut', 'dictia': {'status': 'check', 'text': 'Inclus par défaut'},
'teams': 'Via M365 Audit séparé', 'teams': {'status': 'warn', 'text': 'Via M365 Audit séparé'},
'otter': 'Logs basiques seulement', 'otter': {'status': 'warn', 'text': 'Logs basiques seulement'},
'whisper': 'À développer soi-même' '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"> <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> <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"> <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>
<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> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -366,32 +401,37 @@
</p> </p>
</div> </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"> <div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
{% for card in [ {% for card in [
{ {
'icon': '🍁', 'icon': svg_pin,
'title': 'Hébergement OVH Beauharnois', '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.' '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)', '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.' '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', '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.' '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', '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.' '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]"> <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> <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> <p class="text-sm text-white/80 leading-relaxed">{{ card.desc | safe }}</p>
</article> </article>
@@ -423,10 +463,13 @@
{% for t in testimonials %} {% for t in testimonials %}
<article class="bg-white p-6 rounded border border-brand-border flex flex-col items-center text-center" <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"> 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"> <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' -%}<span class="text-2xl">⚖️</span> {%- if t.persona == 'avocat' -%}
{%- elif t.persona == 'cpa' -%}<span class="text-2xl">📊</span> <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 == 'municipal' -%}<span class="text-2xl">🏛️</span> {%- 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 -%} {%- endif -%}
</div> </div>
<p class="text-sm font-bold text-brand-navy mb-1">{{ t.placeholder_label | safe }}</p> <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"> <div class="flex flex-col sm:flex-row gap-4 justify-center">
{% from 'macros/button.html' import button %} {% 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') }} {{ button('Voir les forfaits', href='#tarifs', variant='ghost', size='lg') }}
</div> </div>

View File

@@ -47,6 +47,7 @@
<th scope="col" class="p-4 font-bold text-brand-navy">DictIA Cloud</th> <th scope="col" class="p-4 font-bold text-brand-navy">DictIA Cloud</th>
</tr> </tr>
</thead> </thead>
{%- 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 text-brand-b3" aria-label="Inclus" role="img"><path d="M5 13l4 4L19 7"/></svg>' -%}
<tbody class="divide-y divide-brand-border"> <tbody class="divide-y divide-brand-border">
{% for row in [ {% for row in [
{'name': 'Hébergement', 'd8': 'Sur place (vos murs)', 'd16': 'Sur place (vos murs)', 'cloud': 'OVH Beauharnois (QC)'}, {'name': 'Hébergement', 'd8': 'Sur place (vos murs)', 'd16': 'Sur place (vos murs)', 'cloud': 'OVH Beauharnois (QC)'},
@@ -54,8 +55,8 @@
{'name': 'Volume audio', 'd8': 'Illimité', 'd16': 'Illimité', 'cloud': 'Illimité'}, {'name': 'Volume audio', 'd8': 'Illimité', 'd16': 'Illimité', 'cloud': 'Illimité'},
{'name': 'Utilisateurs', 'd8': 'Illimité', 'd16': 'Illimité', 'cloud': 'Illimité'}, {'name': 'Utilisateurs', 'd8': 'Illimité', 'd16': 'Illimité', 'cloud': 'Illimité'},
{'name': 'Diarisation', 'd8': '8 locuteurs', 'd16': '8 locuteurs', 'cloud': '8 locuteurs'}, {'name': 'Diarisation', 'd8': '8 locuteurs', 'd16': '8 locuteurs', 'cloud': '8 locuteurs'},
{'name': 'Résumés Mistral 7B local', 'd8': '—', 'd16': '✓', 'cloud': '✓ (mutualisé)'}, {'name': 'Résumés Mistral 7B local', 'd8': '—', 'd16': svg_check, 'cloud': svg_check ~ '<span class="ml-1">(mutualisé)</span>'},
{'name': 'Q&amp;R sur enregistrement', 'd8': '—', 'd16': '✓', 'cloud': '✓'}, {'name': 'Q&amp;R sur enregistrement', 'd8': '—', 'd16': svg_check, 'cloud': svg_check},
{'name': 'Délai de mise en service', 'd8': '~2&nbsp;semaines', 'd16': '~2&nbsp;semaines', 'cloud': '48&nbsp;h'} {'name': 'Délai de mise en service', 'd8': '~2&nbsp;semaines', 'd16': '~2&nbsp;semaines', 'cloud': '48&nbsp;h'}
] %} ] %}
<tr> <tr>
@@ -86,7 +87,7 @@
<div class="divide-y divide-brand-border border-y border-brand-border"> <div class="divide-y divide-brand-border border-y border-brand-border">
{% for item in [ {% 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&nbsp;: les taxes (TPS&nbsp;5&nbsp;% + TVQ&nbsp;9,975&nbsp;%) 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': '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&nbsp;: les taxes (TPS&nbsp;5&nbsp;% + TVQ&nbsp;9,975&nbsp;%) 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 <a href="/legal/conditions" class="grad-text underline">conditions d\'utilisation</a>.'}, {'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 <a href="/legal/conditions" class="grad-text underline">conditions d\'utilisation</a>.'},
{'q': 'Le tarif on-premise inclut-il le matériel GPU?', 'a': 'Le tarif setup (3&nbsp;450&nbsp;$ pour DictIA 8 ou 5&nbsp;750&nbsp;$ 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&nbsp;; nous fournissons une liste de cartes RTX recommandées (RTX 4060 8&nbsp;Go pour DictIA 8, RTX 4080/5080 16&nbsp;Go pour DictIA 16) et pouvons faire l\'achat pour vous moyennant marge transparente.'}, {'q': 'Le tarif on-premise inclut-il le matériel GPU?', 'a': 'Le tarif setup (3&nbsp;450&nbsp;$ pour DictIA 8 ou 5&nbsp;750&nbsp;$ 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&nbsp;; nous fournissons une liste de cartes RTX recommandées (RTX 4060 8&nbsp;Go pour DictIA 8, RTX 4080/5080 16&nbsp;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': '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&nbsp;%). Écrivez à <a href="mailto:info@dictia.ca" class="grad-text underline">info@dictia.ca</a> pour un devis.'} {'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&nbsp;%). Écrivez à <a href="mailto:info@dictia.ca" class="grad-text underline">info@dictia.ca</a> pour un devis.'}
@@ -127,7 +128,7 @@
</p> </p>
<div class="flex flex-col sm:flex-row gap-4 justify-center"> <div class="flex flex-col sm:flex-row gap-4 justify-center">
{% from 'macros/button.html' import button %} {% 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='<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 fonctionnalités', href='/fonctionnalites', variant='ghost', size='lg') }} {{ button('Voir les fonctionnalités', href='/fonctionnalites', variant='ghost', size='lg') }}
</div> </div>
</div> </div>

View File

@@ -611,18 +611,28 @@ def test_no_unverifiable_competitor_claims():
def test_comparatif_check_marks_consistently_mean_good(): 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. 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() client = app.test_client()
body = client.get('/').data.decode('utf-8') 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 assert 'Souveraineté hors Cloud Act' in body
# And must NOT have the legacy inverted form # And must NOT have the legacy inverted form
assert 'Exposé au Cloud Act' not in body, "Row 2 must be reworded to positive convention" 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) # Teams cell for the territoriality criterion: must include the "Non conforme" SVG
assert '✗ Soumis Cloud Act' in body, "Teams must show ✗ for non-Loi-25-compliant transfer" # 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(): def test_testimonials_section_present_with_placeholder_cards():