feat(marketing): round 3 — hero remplacé par 3-step flow canonique + 99+ langues + Cégeps spotlight + CyberPerformance + FAQ enrichie
Hero (templates/marketing/landing.html) : - Reproduction fidèle de dictia.ca/solutions/dictai (source : Website-Sanity/components/sections/dictai-page-content.tsx lignes 260-518) - REMPLACE le mockup app DictIA par le 3-step flow inline canonique (Importez → Texte 2 min → Résumé + actions) - Wordmark large « DictIA » (style production) + H2 cyan « Transcription IA locale en 2 minutes — conforme Barreau, CPA Québec et ChAD » - Sub canonique référençant OVH Beauharnois, Cadre IA MCN, 5 ordres à directives IA formelles - Stats grid (4 col) : ~2 min · 5 ordres · 95 %+ · 0 $ (NBSP OQLF) - Eyebrow back-link « Toutes les solutions » - 5 animations Framer Motion → CSS pure + Alpine.js : 1. 3-step flow auto-cycle 1→2→3 (setInterval 1.8 s, désactivé reduced-motion) 2. Magnetic CTA primary (mousemove → translate max 8 px) 3. Mouse parallax orb 3D (mousemove window → CSS transition) 4. Shockwave on click (CSS pseudo-element scale 0→4 + opacity) 5. Word-staggered title reveal (Dict + IA via animation-delay) Sections enrichies / ajoutées : - Pipeline : sous-titre « Du fichier au résumé — en temps réel » + hint canonique - NEW « 99+ langues détectées » + carte « IA Mistral 7B (LOCAL) » 4 bullets - Pricing : sous-titres canoniques par forfait + note « Tous les prix en CAD, taxes en sus (TPS 5 % + TVQ 9,975 %) » - Conformité : 3 chips claims (~192 000 pros · 5 ordres · 0 donnée hors-Québec) + phrase secteurs réglementés - NEW Cégeps spotlight « Conformité au 19 juin 2026 » avec Cadre IA MCN détaillé (7 bullets, 9 chips organismes, badge pulse glow) - NEW Partenaire CyberPerformance (card horizontale + lien externe) - FAQ : enrichie de 7 → 10 questions canoniques sourcées de dictai-page-content.tsx (Teams Copilot, Otter.ai, Barreau, Clio Manage, etc.) - CTA final : « Prêt à protéger vos données ? » + bouton « Réserver ma démo gratuite » (préserve mailto pré-inscription) Tests : - Ajout tests/conftest.py (stub fcntl POSIX + env vars test) pour permettre exécution sur Windows - Mise à jour 8 assertions liées au nouveau hero, FAQ 10 Q, CTA renforcé, NBSP OQLF dans eyebrow - 61 passed / 3 pré-existant échecs baseline (/blog dans nav + footer + trust-bar phrasing) Contraintes respectées : zéro JS externe, aucun emoji (SVG inline aria-hidden), V3 radii (rounded/rounded-full), brand tokens, OQLF NBSP partout, WCAG (aria-labels, focus-visible, prefers-reduced-motion désactive toutes les animations hero). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,36 +31,49 @@ TESTIMONIALS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# FAQ — 7 verifiable Q&A. Each question/answer must remain factually defensible
|
# FAQ — 10 verifiable Q&A enrichies depuis Website-Sanity/components/sections/dictai-page-content.tsx
|
||||||
# (LPC art. 219). Answers reference public sources where applicable.
|
# (round 3 — synchronisation avec source canonique production dictia.ca/solutions/dictai).
|
||||||
|
# Chaque question/réponse doit rester factuellement défendable (LPC art. 219).
|
||||||
FAQ = [
|
FAQ = [
|
||||||
{
|
{
|
||||||
'q': 'Comment DictIA est-il conforme à la Loi 25 (RPRP)?',
|
'q': 'Comment fonctionne la transcription?',
|
||||||
'a': 'DictIA héberge les données chez OVHcloud Beauharnois (Québec), produit un audit trail intégré conforme à l\'art. 3.5 LPRPSP, fournit un modèle d\'évaluation des facteurs relatifs à la vie privée (EFVP) art. 3.3, et trace les consentements art. 14. Code source AGPL v3 entièrement vérifiable.',
|
'a': 'DictIA utilise WhisperX Large-v3, le moteur de transcription de pointe d\'OpenAI, exécuté directement sur le GPU local (DictIA 8 et 16) ou sur un GPU cloud dédié au Québec (DictIA Cloud). Vous téléversez un fichier audio ou vidéo, et la transcription est générée automatiquement avec identification des locuteurs. Pour la conformité Loi 25, l\'audit trail (art. 3.5 LPRPSP), le registre des consentements (art. 14) et l\'EFVP (art. 3.3) sont fournis par défaut.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'q': 'Quelle est la différence entre DictIA Cloud et DictIA on-premise?',
|
'q': 'Quels formats audio/vidéo sont supportés?',
|
||||||
'a': 'DictIA Cloud (à partir de 369 $/mois) est hébergé chez OVH Beauharnois — aucun matériel à gérer, opérationnel sous 48 h. DictIA on-premise (DictIA 8 et DictIA 16, à partir de 3 450 $ + 173 $/mois) tourne sur GPU dans vos murs — vos données ne sortent jamais de votre réseau.',
|
'a': 'DictIA accepte tous les formats courants : MP3, WAV, M4A, FLAC, OGG, MP4, MKV, WEBM, et plus encore. Aucune conversion préalable nécessaire. Les exports natifs incluent DOCX, PDF, SRT, VTT, TXT, JSON et MD. Modèles spécifiques disponibles pour avocats (interrogatoire numéroté), notaires (procès-verbal d\'assemblée) et CPA (transcription d\'entrevue).',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'q': 'Quelle précision puis-je attendre pour le français québécois?',
|
'q': 'Combien de temps pour transcrire 1 heure d\'audio?',
|
||||||
'a': 'DictIA utilise WhisperX Large-v3 fine-tuné sur audio professionnel québécois. La précision typique observée sur nos jeux de tests internes dépasse 95 %. La méthodologie complète (corpus, métriques WER, conditions) est disponible sur demande : <a href="mailto:info@dictia.ca" class="grad-text underline">info@dictia.ca</a>.',
|
'a': 'Environ 2 minutes sur GPU. C\'est 99 % plus rapide que la transcription manuelle, qui prend typiquement 4 à 6 heures pour 1 heure d\'audio. La précision typique observée sur nos jeux de tests internes dépasse 95 % en français canadien. Méthodologie complète disponible sur demande : <a href="mailto:info@dictia.ca" class="grad-text underline">info@dictia.ca</a>.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'q': 'Pouvez-vous transcrire des audiences ou des interrogatoires confidentiels?',
|
'q': 'La transcription est-elle vraiment confidentielle?',
|
||||||
'a': 'Oui, à condition que vous respectiez les obligations de votre ordre (Barreau, Chambre des notaires, etc.) en matière de consentement et de confidentialité. DictIA on-premise est recommandé pour ce type d\'usage : les données ne quittent jamais votre infrastructure. Consultez votre ordre avant tout déploiement.',
|
'a': 'Avec DictIA 8 et 16, vos données ne quittent jamais votre bureau — le traitement est 100 % local, sans connexion internet requise. Avec DictIA Cloud, les données sont hébergées exclusivement au Canada (OVH Beauharnois, QC et GCP Toronto, ON). Aucun transfert hors-frontières, zéro Cloud Act.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'q': 'Quels formats d\'export sont supportés?',
|
'q': 'Teams Copilot est-il légal pour mes réunions?',
|
||||||
'a': 'DOCX, PDF, SRT, VTT, TXT, JSON et MD. Modèles spécifiques disponibles pour avocats (interrogatoire numéroté), notaires (procès-verbal d\'assemblée) et CPA (transcription d\'entrevue). Intégrations natives : Word, Outlook, Teams, Notion, Obsidian, Zapier, Make, n8n.',
|
'a': 'Non. Teams Copilot envoie les transcriptions vers des serveurs Microsoft soumis au Cloud Act américain. La Loi 25 (art. 44-45) exige un consentement explicite pour transmettre des données biométriques (voix) hors du Québec. Depuis septembre 2023, toute transcription sur Teams Copilot est en violation — sans exception.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'q': 'Que se passe-t-il si je résilie mon abonnement?',
|
'q': 'Otter.ai est-il en violation?',
|
||||||
'a': 'Vos données restent exportables pendant 90 jours après résiliation (DOCX/PDF/JSON). Passé ce délai, suppression définitive avec confirmation écrite — politique conforme à l\'art. 23 LPRPSP. Aucun frais de résiliation. Détails dans nos <a href="/legal/conditions" class="grad-text underline">conditions d\'utilisation</a>.',
|
'a': 'Oui. Otter.ai héberge les données sur AWS us-east-1 (Virginie, USA). Vos enregistrements de réunions — y compris les discussions confidentielles avec vos clients — transitent et sont stockés sur des serveurs américains soumis au Cloud Act. C\'est une violation de la Loi 25 depuis septembre 2023.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'q': 'Le code source est-il vraiment open source AGPL v3?',
|
'q': 'Que dit le Barreau du Québec sur l\'IA?',
|
||||||
'a': 'Oui. Code source complet sur <a href="https://gitea.innova-ai.ca/Innova-AI/dictia-public" target="_blank" rel="noopener" class="grad-text underline">Gitea public</a>. Conséquence pratique de l\'AGPL : tout fork hébergé doit publier ses modifications. Transparence vérifiable par vos auditeurs internes ou un tiers de confiance.',
|
'a': 'En octobre 2024, le Barreau a émis une directive interdisant explicitement l\'utilisation d\'outils IA qui envoient des données client vers des serveurs étrangers. Une violation peut entraîner des sanctions disciplinaires. DictIA est conçu comme une solution conforme au Code de déontologie du Barreau (architecture mappée — voir notre page <a href="/conformite" class="grad-text underline">Conformité</a>).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'q': 'DictIA s\'intègre-t-il à Clio Manage ou PCLaw?',
|
||||||
|
'a': 'L\'intégration native Clio Manage est prévue pour Q1 2026. En attendant, DictIA exporte nativement en DOCX, compatible avec tous les logiciels de gestion de dossiers. L\'importation manuelle prend moins de 30 secondes par transcription. Intégrations natives disponibles : Word, Outlook, Teams, Notion, Obsidian, Zapier, Make, n8n.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'q': 'Ai-je besoin de connaissances techniques?',
|
||||||
|
'a': 'Non. DictIA est une solution clé en main : nous fournissons le matériel (solutions locales), installons tout sur site, formons votre équipe et assurons la maintenance mensuelle à distance. Vous n\'avez besoin d\'aucune expertise technique. En cas de résiliation, vos données restent exportables pendant 90 jours (art. 23 LPRPSP).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'q': 'DictIA est-il open source?',
|
||||||
|
'a': 'Oui. Le code source est sous licence AGPL v3 — transparence totale. La stack complète (WhisperX, pyannote, Mistral, Ollama, FastAPI, PostgreSQL) est 100 % open source, sans aucune redevance logicielle. Code source complet sur <a href="https://gitea.innova-ai.ca/Innova-AI/dictia-public" target="_blank" rel="noopener" class="grad-text underline">Gitea public</a>. Conséquence pratique de l\'AGPL : tout fork hébergé doit publier ses modifications.',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -361,8 +361,8 @@
|
|||||||
.sticky {
|
.sticky {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
}
|
}
|
||||||
.-inset-8 {
|
.-inset-1 {
|
||||||
inset: calc(var(--spacing) * -8);
|
inset: calc(var(--spacing) * -1);
|
||||||
}
|
}
|
||||||
.inset-0 {
|
.inset-0 {
|
||||||
inset: calc(var(--spacing) * 0);
|
inset: calc(var(--spacing) * 0);
|
||||||
@@ -370,6 +370,9 @@
|
|||||||
.inset-\[-8px\] {
|
.inset-\[-8px\] {
|
||||||
inset: -8px;
|
inset: -8px;
|
||||||
}
|
}
|
||||||
|
.inset-\[30\%\] {
|
||||||
|
inset: 30%;
|
||||||
|
}
|
||||||
.inset-x-0 {
|
.inset-x-0 {
|
||||||
inset-inline: calc(var(--spacing) * 0);
|
inset-inline: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
@@ -382,17 +385,14 @@
|
|||||||
.end {
|
.end {
|
||||||
inset-inline-end: var(--spacing);
|
inset-inline-end: var(--spacing);
|
||||||
}
|
}
|
||||||
.-top-1 {
|
|
||||||
top: calc(var(--spacing) * -1);
|
|
||||||
}
|
|
||||||
.-top-1\.5 {
|
.-top-1\.5 {
|
||||||
top: calc(var(--spacing) * -1.5);
|
top: calc(var(--spacing) * -1.5);
|
||||||
}
|
}
|
||||||
.-top-3 {
|
.-top-3 {
|
||||||
top: calc(var(--spacing) * -3);
|
top: calc(var(--spacing) * -3);
|
||||||
}
|
}
|
||||||
.-top-10 {
|
.-top-12 {
|
||||||
top: calc(var(--spacing) * -10);
|
top: calc(var(--spacing) * -12);
|
||||||
}
|
}
|
||||||
.top-0 {
|
.top-0 {
|
||||||
top: calc(var(--spacing) * 0);
|
top: calc(var(--spacing) * 0);
|
||||||
@@ -424,6 +424,9 @@
|
|||||||
.top-16 {
|
.top-16 {
|
||||||
top: calc(var(--spacing) * 16);
|
top: calc(var(--spacing) * 16);
|
||||||
}
|
}
|
||||||
|
.top-\[12\%\] {
|
||||||
|
top: 12%;
|
||||||
|
}
|
||||||
.top-\[42px\] {
|
.top-\[42px\] {
|
||||||
top: 42px;
|
top: 42px;
|
||||||
}
|
}
|
||||||
@@ -436,8 +439,8 @@
|
|||||||
.-right-1\.5 {
|
.-right-1\.5 {
|
||||||
right: calc(var(--spacing) * -1.5);
|
right: calc(var(--spacing) * -1.5);
|
||||||
}
|
}
|
||||||
.-right-6 {
|
.-right-12 {
|
||||||
right: calc(var(--spacing) * -6);
|
right: calc(var(--spacing) * -12);
|
||||||
}
|
}
|
||||||
.right-0 {
|
.right-0 {
|
||||||
right: calc(var(--spacing) * 0);
|
right: calc(var(--spacing) * 0);
|
||||||
@@ -469,8 +472,8 @@
|
|||||||
.right-\[3\%\] {
|
.right-\[3\%\] {
|
||||||
right: 3%;
|
right: 3%;
|
||||||
}
|
}
|
||||||
.-bottom-10 {
|
.right-\[6\%\] {
|
||||||
bottom: calc(var(--spacing) * -10);
|
right: 6%;
|
||||||
}
|
}
|
||||||
.bottom-0 {
|
.bottom-0 {
|
||||||
bottom: calc(var(--spacing) * 0);
|
bottom: calc(var(--spacing) * 0);
|
||||||
@@ -487,9 +490,6 @@
|
|||||||
.bottom-full {
|
.bottom-full {
|
||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
}
|
}
|
||||||
.-left-10 {
|
|
||||||
left: calc(var(--spacing) * -10);
|
|
||||||
}
|
|
||||||
.left-0 {
|
.left-0 {
|
||||||
left: calc(var(--spacing) * 0);
|
left: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
@@ -586,9 +586,6 @@
|
|||||||
.-mx-4 {
|
.-mx-4 {
|
||||||
margin-inline: calc(var(--spacing) * -4);
|
margin-inline: calc(var(--spacing) * -4);
|
||||||
}
|
}
|
||||||
.mx-1 {
|
|
||||||
margin-inline: calc(var(--spacing) * 1);
|
|
||||||
}
|
|
||||||
.mx-3 {
|
.mx-3 {
|
||||||
margin-inline: calc(var(--spacing) * 3);
|
margin-inline: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
@@ -673,9 +670,6 @@
|
|||||||
.mr-4 {
|
.mr-4 {
|
||||||
margin-right: calc(var(--spacing) * 4);
|
margin-right: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
.-mb-\[0\.5rem\] {
|
|
||||||
margin-bottom: calc(0.5rem * -1);
|
|
||||||
}
|
|
||||||
.-mb-px {
|
.-mb-px {
|
||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
}
|
}
|
||||||
@@ -880,9 +874,6 @@
|
|||||||
.h-\[450px\] {
|
.h-\[450px\] {
|
||||||
height: 450px;
|
height: 450px;
|
||||||
}
|
}
|
||||||
.h-\[460px\] {
|
|
||||||
height: 460px;
|
|
||||||
}
|
|
||||||
.h-\[500px\] {
|
.h-\[500px\] {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
}
|
}
|
||||||
@@ -946,6 +937,9 @@
|
|||||||
.min-h-\[8rem\] {
|
.min-h-\[8rem\] {
|
||||||
min-height: 8rem;
|
min-height: 8rem;
|
||||||
}
|
}
|
||||||
|
.min-h-\[85vh\] {
|
||||||
|
min-height: 85vh;
|
||||||
|
}
|
||||||
.min-h-\[110px\] {
|
.min-h-\[110px\] {
|
||||||
min-height: 110px;
|
min-height: 110px;
|
||||||
}
|
}
|
||||||
@@ -1027,9 +1021,6 @@
|
|||||||
.w-56 {
|
.w-56 {
|
||||||
width: calc(var(--spacing) * 56);
|
width: calc(var(--spacing) * 56);
|
||||||
}
|
}
|
||||||
.w-64 {
|
|
||||||
width: calc(var(--spacing) * 64);
|
|
||||||
}
|
|
||||||
.w-72 {
|
.w-72 {
|
||||||
width: calc(var(--spacing) * 72);
|
width: calc(var(--spacing) * 72);
|
||||||
}
|
}
|
||||||
@@ -1045,6 +1036,9 @@
|
|||||||
.w-\[68px\] {
|
.w-\[68px\] {
|
||||||
width: 68px;
|
width: 68px;
|
||||||
}
|
}
|
||||||
|
.w-\[80px\] {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
.w-\[88px\] {
|
.w-\[88px\] {
|
||||||
width: 88px;
|
width: 88px;
|
||||||
}
|
}
|
||||||
@@ -1233,9 +1227,6 @@
|
|||||||
.transform {
|
.transform {
|
||||||
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
||||||
}
|
}
|
||||||
.animate-plus-breathe {
|
|
||||||
animation: plus-breathe 2s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
.animate-pulse {
|
.animate-pulse {
|
||||||
animation: var(--animate-pulse);
|
animation: var(--animate-pulse);
|
||||||
}
|
}
|
||||||
@@ -1304,15 +1295,15 @@
|
|||||||
.grid-cols-4 {
|
.grid-cols-4 {
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
.grid-cols-6 {
|
||||||
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||||
|
}
|
||||||
.grid-cols-7 {
|
.grid-cols-7 {
|
||||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
.grid-cols-\[2fr_2fr_3fr\] {
|
.grid-cols-\[2fr_2fr_3fr\] {
|
||||||
grid-template-columns: 2fr 2fr 3fr;
|
grid-template-columns: 2fr 2fr 3fr;
|
||||||
}
|
}
|
||||||
.grid-cols-\[180px_1fr_170px\] {
|
|
||||||
grid-template-columns: 180px 1fr 170px;
|
|
||||||
}
|
|
||||||
.flex-col {
|
.flex-col {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -1379,9 +1370,6 @@
|
|||||||
.gap-8 {
|
.gap-8 {
|
||||||
gap: calc(var(--spacing) * 8);
|
gap: calc(var(--spacing) * 8);
|
||||||
}
|
}
|
||||||
.gap-12 {
|
|
||||||
gap: calc(var(--spacing) * 12);
|
|
||||||
}
|
|
||||||
.gap-\[1\.5px\] {
|
.gap-\[1\.5px\] {
|
||||||
gap: 1.5px;
|
gap: 1.5px;
|
||||||
}
|
}
|
||||||
@@ -1413,6 +1401,13 @@
|
|||||||
margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
|
margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.space-y-2\.5 {
|
||||||
|
:where(& > :not(:last-child)) {
|
||||||
|
--tw-space-y-reverse: 0;
|
||||||
|
margin-block-start: calc(calc(var(--spacing) * 2.5) * var(--tw-space-y-reverse));
|
||||||
|
margin-block-end: calc(calc(var(--spacing) * 2.5) * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
}
|
||||||
|
}
|
||||||
.space-y-3 {
|
.space-y-3 {
|
||||||
:where(& > :not(:last-child)) {
|
:where(& > :not(:last-child)) {
|
||||||
--tw-space-y-reverse: 0;
|
--tw-space-y-reverse: 0;
|
||||||
@@ -1631,6 +1626,9 @@
|
|||||||
--tw-border-style: dashed;
|
--tw-border-style: dashed;
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
}
|
}
|
||||||
|
.\!border-transparent {
|
||||||
|
border-color: transparent !important;
|
||||||
|
}
|
||||||
.border-\[var\(--bg-accent\)\] {
|
.border-\[var\(--bg-accent\)\] {
|
||||||
border-color: var(--bg-accent);
|
border-color: var(--bg-accent);
|
||||||
}
|
}
|
||||||
@@ -1712,6 +1710,9 @@
|
|||||||
.border-brand-b2\/40 {
|
.border-brand-b2\/40 {
|
||||||
border-color: color-mix(in oklab, #00bdd8 40%, transparent);
|
border-color: color-mix(in oklab, #00bdd8 40%, transparent);
|
||||||
}
|
}
|
||||||
|
.border-brand-b3\/15 {
|
||||||
|
border-color: color-mix(in oklab, #00c896 15%, transparent);
|
||||||
|
}
|
||||||
.border-brand-b3\/60 {
|
.border-brand-b3\/60 {
|
||||||
border-color: color-mix(in oklab, #00c896 60%, transparent);
|
border-color: color-mix(in oklab, #00c896 60%, transparent);
|
||||||
}
|
}
|
||||||
@@ -1823,6 +1824,12 @@
|
|||||||
border-color: color-mix(in oklab, var(--color-white) 6%, transparent);
|
border-color: color-mix(in oklab, var(--color-white) 6%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.border-white\/\[0\.07\] {
|
||||||
|
border-color: color-mix(in srgb, #fff 7.000000000000001%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
border-color: color-mix(in oklab, var(--color-white) 7.000000000000001%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
.border-white\/\[0\.08\] {
|
.border-white\/\[0\.08\] {
|
||||||
border-color: color-mix(in srgb, #fff 8%, transparent);
|
border-color: color-mix(in srgb, #fff 8%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -2015,6 +2022,9 @@
|
|||||||
.bg-brand-b1\/\[0\.06\] {
|
.bg-brand-b1\/\[0\.06\] {
|
||||||
background-color: color-mix(in oklab, #0062ff 6%, transparent);
|
background-color: color-mix(in oklab, #0062ff 6%, transparent);
|
||||||
}
|
}
|
||||||
|
.bg-brand-b3 {
|
||||||
|
background-color: #00c896;
|
||||||
|
}
|
||||||
.bg-brand-b3\/10 {
|
.bg-brand-b3\/10 {
|
||||||
background-color: color-mix(in oklab, #00c896 10%, transparent);
|
background-color: color-mix(in oklab, #00c896 10%, transparent);
|
||||||
}
|
}
|
||||||
@@ -2054,12 +2064,6 @@
|
|||||||
.bg-brand-navy2 {
|
.bg-brand-navy2 {
|
||||||
background-color: #0b1525;
|
background-color: #0b1525;
|
||||||
}
|
}
|
||||||
.bg-emerald-500\/20 {
|
|
||||||
background-color: color-mix(in srgb, oklch(69.6% 0.17 162.48) 20%, transparent);
|
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
|
||||||
background-color: color-mix(in oklab, var(--color-emerald-500) 20%, transparent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bg-emerald-600 {
|
.bg-emerald-600 {
|
||||||
background-color: var(--color-emerald-600);
|
background-color: var(--color-emerald-600);
|
||||||
}
|
}
|
||||||
@@ -2255,12 +2259,6 @@
|
|||||||
background-color: color-mix(in oklab, var(--color-white) 6%, transparent);
|
background-color: color-mix(in oklab, var(--color-white) 6%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.bg-white\/\[0\.08\] {
|
|
||||||
background-color: color-mix(in srgb, #fff 8%, transparent);
|
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
|
||||||
background-color: color-mix(in oklab, var(--color-white) 8%, transparent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bg-yellow-50 {
|
.bg-yellow-50 {
|
||||||
background-color: var(--color-yellow-50);
|
background-color: var(--color-yellow-50);
|
||||||
}
|
}
|
||||||
@@ -2315,6 +2313,10 @@
|
|||||||
--tw-gradient-from: color-mix(in oklab, #0062ff 6%, transparent);
|
--tw-gradient-from: color-mix(in oklab, #0062ff 6%, transparent);
|
||||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
}
|
}
|
||||||
|
.from-brand-b3 {
|
||||||
|
--tw-gradient-from: #00c896;
|
||||||
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
|
}
|
||||||
.from-orange-500 {
|
.from-orange-500 {
|
||||||
--tw-gradient-from: var(--color-orange-500);
|
--tw-gradient-from: var(--color-orange-500);
|
||||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
@@ -2354,6 +2356,10 @@
|
|||||||
--tw-gradient-to: var(--color-amber-600);
|
--tw-gradient-to: var(--color-amber-600);
|
||||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
}
|
}
|
||||||
|
.to-brand-b1 {
|
||||||
|
--tw-gradient-to: #0062ff;
|
||||||
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
|
}
|
||||||
.to-brand-b3\/10 {
|
.to-brand-b3\/10 {
|
||||||
--tw-gradient-to: color-mix(in oklab, #00c896 10%, transparent);
|
--tw-gradient-to: color-mix(in oklab, #00c896 10%, transparent);
|
||||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
@@ -2533,6 +2539,9 @@
|
|||||||
.pt-4 {
|
.pt-4 {
|
||||||
padding-top: calc(var(--spacing) * 4);
|
padding-top: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
.pt-5 {
|
||||||
|
padding-top: calc(var(--spacing) * 5);
|
||||||
|
}
|
||||||
.pt-6 {
|
.pt-6 {
|
||||||
padding-top: calc(var(--spacing) * 6);
|
padding-top: calc(var(--spacing) * 6);
|
||||||
}
|
}
|
||||||
@@ -2572,9 +2581,6 @@
|
|||||||
.pb-0 {
|
.pb-0 {
|
||||||
padding-bottom: calc(var(--spacing) * 0);
|
padding-bottom: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
.pb-1 {
|
|
||||||
padding-bottom: calc(var(--spacing) * 1);
|
|
||||||
}
|
|
||||||
.pb-2 {
|
.pb-2 {
|
||||||
padding-bottom: calc(var(--spacing) * 2);
|
padding-bottom: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -2638,6 +2644,9 @@
|
|||||||
.font-mono {
|
.font-mono {
|
||||||
font-family: JetBrains Mono Variable, JetBrains Mono, monospace;
|
font-family: JetBrains Mono Variable, JetBrains Mono, monospace;
|
||||||
}
|
}
|
||||||
|
.font-sans {
|
||||||
|
font-family: Inter Variable, Inter, system-ui, sans-serif;
|
||||||
|
}
|
||||||
.text-2xl {
|
.text-2xl {
|
||||||
font-size: var(--text-2xl);
|
font-size: var(--text-2xl);
|
||||||
line-height: var(--tw-leading, var(--text-2xl--line-height));
|
line-height: var(--tw-leading, var(--text-2xl--line-height));
|
||||||
@@ -2684,6 +2693,9 @@
|
|||||||
.text-\[9px\] {
|
.text-\[9px\] {
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
}
|
}
|
||||||
|
.text-\[10\.5px\] {
|
||||||
|
font-size: 10.5px;
|
||||||
|
}
|
||||||
.text-\[10px\] {
|
.text-\[10px\] {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
@@ -2708,9 +2720,6 @@
|
|||||||
.text-\[clamp\(1\.75rem\,2\.5vw\,2\.25rem\)\] {
|
.text-\[clamp\(1\.75rem\,2\.5vw\,2\.25rem\)\] {
|
||||||
font-size: clamp(1.75rem, 2.5vw, 2.25rem);
|
font-size: clamp(1.75rem, 2.5vw, 2.25rem);
|
||||||
}
|
}
|
||||||
.text-\[clamp\(2\.5rem\,4vw\,4rem\)\] {
|
|
||||||
font-size: clamp(2.5rem, 4vw, 4rem);
|
|
||||||
}
|
|
||||||
.text-\[clamp\(2\.25rem\,4vw\,3\.5rem\)\] {
|
.text-\[clamp\(2\.25rem\,4vw\,3\.5rem\)\] {
|
||||||
font-size: clamp(2.25rem, 4vw, 3.5rem);
|
font-size: clamp(2.25rem, 4vw, 3.5rem);
|
||||||
}
|
}
|
||||||
@@ -2727,6 +2736,10 @@
|
|||||||
--tw-leading: calc(var(--spacing) * 5);
|
--tw-leading: calc(var(--spacing) * 5);
|
||||||
line-height: calc(var(--spacing) * 5);
|
line-height: calc(var(--spacing) * 5);
|
||||||
}
|
}
|
||||||
|
.leading-\[0\.92\] {
|
||||||
|
--tw-leading: 0.92;
|
||||||
|
line-height: 0.92;
|
||||||
|
}
|
||||||
.leading-\[1\.05\] {
|
.leading-\[1\.05\] {
|
||||||
--tw-leading: 1.05;
|
--tw-leading: 1.05;
|
||||||
line-height: 1.05;
|
line-height: 1.05;
|
||||||
@@ -2771,10 +2784,6 @@
|
|||||||
--tw-tracking: 0.2em;
|
--tw-tracking: 0.2em;
|
||||||
letter-spacing: 0.2em;
|
letter-spacing: 0.2em;
|
||||||
}
|
}
|
||||||
.tracking-\[0\.12em\] {
|
|
||||||
--tw-tracking: 0.12em;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
}
|
|
||||||
.tracking-\[0\.14em\] {
|
.tracking-\[0\.14em\] {
|
||||||
--tw-tracking: 0.14em;
|
--tw-tracking: 0.14em;
|
||||||
letter-spacing: 0.14em;
|
letter-spacing: 0.14em;
|
||||||
@@ -2827,6 +2836,9 @@
|
|||||||
.whitespace-pre-wrap {
|
.whitespace-pre-wrap {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
.\!text-white {
|
||||||
|
color: var(--color-white) !important;
|
||||||
|
}
|
||||||
.text-\[var\(--bg-accent\)\] {
|
.text-\[var\(--bg-accent\)\] {
|
||||||
color: var(--bg-accent);
|
color: var(--bg-accent);
|
||||||
}
|
}
|
||||||
@@ -2989,9 +3001,6 @@
|
|||||||
.text-cyan-300 {
|
.text-cyan-300 {
|
||||||
color: var(--color-cyan-300);
|
color: var(--color-cyan-300);
|
||||||
}
|
}
|
||||||
.text-emerald-300 {
|
|
||||||
color: var(--color-emerald-300);
|
|
||||||
}
|
|
||||||
.text-emerald-500 {
|
.text-emerald-500 {
|
||||||
color: var(--color-emerald-500);
|
color: var(--color-emerald-500);
|
||||||
}
|
}
|
||||||
@@ -3052,9 +3061,6 @@
|
|||||||
.text-red-100 {
|
.text-red-100 {
|
||||||
color: var(--color-red-100);
|
color: var(--color-red-100);
|
||||||
}
|
}
|
||||||
.text-red-300 {
|
|
||||||
color: var(--color-red-300);
|
|
||||||
}
|
|
||||||
.text-red-400 {
|
.text-red-400 {
|
||||||
color: var(--color-red-400);
|
color: var(--color-red-400);
|
||||||
}
|
}
|
||||||
@@ -3151,6 +3157,12 @@
|
|||||||
color: color-mix(in oklab, var(--color-white) 60%, transparent);
|
color: color-mix(in oklab, var(--color-white) 60%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.text-white\/65 {
|
||||||
|
color: color-mix(in srgb, #fff 65%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
color: color-mix(in oklab, var(--color-white) 65%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
.text-white\/70 {
|
.text-white\/70 {
|
||||||
color: color-mix(in srgb, #fff 70%, transparent);
|
color: color-mix(in srgb, #fff 70%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -3163,10 +3175,10 @@
|
|||||||
color: color-mix(in oklab, var(--color-white) 80%, transparent);
|
color: color-mix(in oklab, var(--color-white) 80%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.text-white\/90 {
|
.text-white\/85 {
|
||||||
color: color-mix(in srgb, #fff 90%, transparent);
|
color: color-mix(in srgb, #fff 85%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
color: color-mix(in oklab, var(--color-white) 90%, transparent);
|
color: color-mix(in oklab, var(--color-white) 85%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.text-yellow-400 {
|
.text-yellow-400 {
|
||||||
@@ -3225,20 +3237,6 @@
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.placeholder-white\/40 {
|
|
||||||
&::-moz-placeholder {
|
|
||||||
color: color-mix(in srgb, #fff 40%, transparent);
|
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
|
||||||
color: color-mix(in oklab, var(--color-white) 40%, transparent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&::placeholder {
|
|
||||||
color: color-mix(in srgb, #fff 40%, transparent);
|
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
|
||||||
color: color-mix(in oklab, var(--color-white) 40%, transparent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.accent-brand-b1 {
|
.accent-brand-b1 {
|
||||||
accent-color: #0062ff;
|
accent-color: #0062ff;
|
||||||
}
|
}
|
||||||
@@ -3326,12 +3324,6 @@
|
|||||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
}
|
}
|
||||||
.shadow-black\/40 {
|
|
||||||
--tw-shadow-color: color-mix(in srgb, #000 40%, transparent);
|
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
|
||||||
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-black) 40%, transparent) var(--tw-shadow-alpha), transparent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ring-\[var\(--border-accent\)\] {
|
.ring-\[var\(--border-accent\)\] {
|
||||||
--tw-ring-color: var(--border-accent);
|
--tw-ring-color: var(--border-accent);
|
||||||
}
|
}
|
||||||
@@ -3359,10 +3351,6 @@
|
|||||||
--tw-blur: blur(8px);
|
--tw-blur: blur(8px);
|
||||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
}
|
}
|
||||||
.blur-3xl {
|
|
||||||
--tw-blur: blur(var(--blur-3xl));
|
|
||||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
|
||||||
}
|
|
||||||
.filter {
|
.filter {
|
||||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
}
|
}
|
||||||
@@ -3494,6 +3482,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.group-hover\:text-brand-b1 {
|
||||||
|
&:is(:where(.group):hover *) {
|
||||||
|
@media (hover: hover) {
|
||||||
|
color: #0062ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.group-hover\:opacity-60 {
|
.group-hover\:opacity-60 {
|
||||||
&:is(:where(.group):hover *) {
|
&:is(:where(.group):hover *) {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -3711,6 +3706,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hover\:border-white\/30 {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
border-color: color-mix(in srgb, #fff 30%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
border-color: color-mix(in oklab, var(--color-white) 30%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.hover\:border-yellow-500\/30 {
|
.hover\:border-yellow-500\/30 {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -4022,16 +4027,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.hover\:bg-white\/\[0\.04\] {
|
|
||||||
&:hover {
|
|
||||||
@media (hover: hover) {
|
|
||||||
background-color: color-mix(in srgb, #fff 4%, transparent);
|
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
|
||||||
background-color: color-mix(in oklab, var(--color-white) 4%, transparent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.hover\:bg-white\/\[0\.05\] {
|
.hover\:bg-white\/\[0\.05\] {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -4058,6 +4053,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hover\:from-brand-b1 {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
--tw-gradient-from: #0062ff;
|
||||||
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.hover\:from-orange-600 {
|
.hover\:from-orange-600 {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -4090,6 +4093,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hover\:to-brand-b3 {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
--tw-gradient-to: #00c896;
|
||||||
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.hover\:to-purple-600 {
|
.hover\:to-purple-600 {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -4516,6 +4527,11 @@
|
|||||||
outline-color: var(--color-red-700);
|
outline-color: var(--color-red-700);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.focus-visible\:outline-white {
|
||||||
|
&:focus-visible {
|
||||||
|
outline-color: var(--color-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
.active\:scale-95 {
|
.active\:scale-95 {
|
||||||
&:active {
|
&:active {
|
||||||
--tw-scale-x: 95%;
|
--tw-scale-x: 95%;
|
||||||
@@ -4669,6 +4685,11 @@
|
|||||||
width: calc(var(--spacing) * 40);
|
width: calc(var(--spacing) * 40);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sm\:w-auto {
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
.sm\:max-w-\[80px\] {
|
.sm\:max-w-\[80px\] {
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
max-width: 80px;
|
max-width: 80px;
|
||||||
@@ -4689,11 +4710,26 @@
|
|||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sm\:grid-cols-4 {
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sm\:grid-cols-8 {
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
grid-template-columns: repeat(8, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
.sm\:flex-row {
|
.sm\:flex-row {
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sm\:flex-nowrap {
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
.sm\:items-center {
|
.sm\:items-center {
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -5118,6 +5154,12 @@
|
|||||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.md\:text-xl {
|
||||||
|
@media (width >= 48rem) {
|
||||||
|
font-size: var(--text-xl);
|
||||||
|
line-height: var(--tw-leading, var(--text-xl--line-height));
|
||||||
|
}
|
||||||
|
}
|
||||||
.md\:text-\[10px\] {
|
.md\:text-\[10px\] {
|
||||||
@media (width >= 48rem) {
|
@media (width >= 48rem) {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
@@ -5224,31 +5266,16 @@
|
|||||||
grid-template-columns: 1fr 240px;
|
grid-template-columns: 1fr 240px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.lg\:grid-cols-\[1fr_minmax\(0\,560px\)\] {
|
|
||||||
@media (width >= 64rem) {
|
|
||||||
grid-template-columns: 1fr minmax(0,560px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lg\:flex-row {
|
.lg\:flex-row {
|
||||||
@media (width >= 64rem) {
|
@media (width >= 64rem) {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.lg\:justify-start {
|
|
||||||
@media (width >= 64rem) {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lg\:gap-10 {
|
.lg\:gap-10 {
|
||||||
@media (width >= 64rem) {
|
@media (width >= 64rem) {
|
||||||
gap: calc(var(--spacing) * 10);
|
gap: calc(var(--spacing) * 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.lg\:gap-16 {
|
|
||||||
@media (width >= 64rem) {
|
|
||||||
gap: calc(var(--spacing) * 16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lg\:border-r {
|
.lg\:border-r {
|
||||||
@media (width >= 64rem) {
|
@media (width >= 64rem) {
|
||||||
border-right-style: var(--tw-border-style);
|
border-right-style: var(--tw-border-style);
|
||||||
@@ -5280,9 +5307,10 @@
|
|||||||
padding-bottom: calc(var(--spacing) * 8);
|
padding-bottom: calc(var(--spacing) * 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.lg\:text-left {
|
.lg\:text-3xl {
|
||||||
@media (width >= 64rem) {
|
@media (width >= 64rem) {
|
||||||
text-align: left;
|
font-size: var(--text-3xl);
|
||||||
|
line-height: var(--tw-leading, var(--text-3xl--line-height));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.xl\:grid-cols-4 {
|
.xl\:grid-cols-4 {
|
||||||
@@ -6132,14 +6160,6 @@
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@keyframes plus-breathe {
|
|
||||||
0%, 100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@layer properties {
|
@layer properties {
|
||||||
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
||||||
*, ::before, ::after, ::backdrop {
|
*, ::before, ::after, ::backdrop {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
'DictIA 8',
|
'DictIA 8',
|
||||||
'3 450 $',
|
'3 450 $',
|
||||||
'173 $',
|
'173 $',
|
||||||
'PME · RH · Manufacturiers',
|
'PME · Manufacturiers · RH · Services — local, vos données ne quittent jamais votre bureau.',
|
||||||
['GPU 8 Go RTX', 'Volume illimité', 'WhisperX FR-CA', 'Diarisation 8 locuteurs', 'Support inclus']
|
['GPU 8 Go RTX', 'Volume illimité', 'WhisperX FR-CA', 'Diarisation 8 locuteurs', 'Support inclus']
|
||||||
) }}
|
) }}
|
||||||
{{ pricing_card(
|
{{ pricing_card(
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
'DictIA 16',
|
'DictIA 16',
|
||||||
'5 750 $',
|
'5 750 $',
|
||||||
'201 $',
|
'201 $',
|
||||||
'Cabinets juridiques · CPA · Finance',
|
'Cabinets juridiques · CPA · Services financiers — local, Mistral 7B sur votre GPU.',
|
||||||
['GPU 16 Go RTX', 'Mistral 7B local', 'Q&R sur enregistrement', 'Tout DictIA 8', 'Support prioritaire'],
|
['GPU 16 Go RTX', 'Mistral 7B local', 'Q&R sur enregistrement', 'Tout DictIA 8', 'Support prioritaire'],
|
||||||
recommended=True
|
recommended=True
|
||||||
) }}
|
) }}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
'DictIA Cloud',
|
'DictIA Cloud',
|
||||||
'0 $',
|
'0 $',
|
||||||
'369 $',
|
'369 $',
|
||||||
'Organismes · Municipalités · Multi-sites',
|
'Organismes · Municipalités · Multi-sites — Cloud QC, opérationnel en 48 h, aucun matériel requis.',
|
||||||
['Hébergé OVH Beauharnois (Québec)', 'Opérationnel sous 48 h', 'Aucun matériel à gérer', 'SLA visé 99,9 %', 'Conforme Loi 25']
|
['Hébergé OVH Beauharnois (Québec)', 'Opérationnel sous 48 h', 'Aucun matériel à gérer', 'SLA visé 99,9 %', 'Conforme Loi 25']
|
||||||
) }}
|
) }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,35 +4,146 @@
|
|||||||
{% block description %}DictIA transcrit vos réunions confidentielles 100% au Québec. Conforme Loi 25, Barreau, CPA, ChAD. Conçu avec 9 ordres professionnels — lancement printemps 2026.{% endblock %}
|
{% block description %}DictIA transcrit vos réunions confidentielles 100% au Québec. Conforme Loi 25, Barreau, CPA, ChAD. Conçu avec 9 ordres professionnels — lancement printemps 2026.{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{# ===== HERO ===== #}
|
{# ===== HERO — round 3 : reproduction fidèle dictia.ca/solutions/dictai ===== #}
|
||||||
{# Local keyframes scoped to the hero — audio progress bar loops 50%→75%→50% over 15s #}
|
{# Source : InnovA-AI/Website-Sanity/components/sections/dictai-page-content.tsx (lignes 260-518)
|
||||||
|
Animations Framer Motion → CSS pure + Alpine.js :
|
||||||
|
1. 3-step flow auto-cycle 1→2→3 (setInterval 1.8 s, désactivé reduced-motion)
|
||||||
|
2. Magnetic CTA primary (mousemove → translate, max 8 px)
|
||||||
|
3. Mouse parallax orb (mousemove window → translate, inertie via CSS transition)
|
||||||
|
4. Shockwave on click (CSS pseudo-element, scale 0→4 + opacity 1→0)
|
||||||
|
5. Hero title fade-in + word-staggered animation #}
|
||||||
<style>
|
<style>
|
||||||
@keyframes audio-progress {
|
/* ── 3-step flow : auto-cycle highlight ── */
|
||||||
0%, 100% { width: 50%; }
|
.hero-flow-card {
|
||||||
50% { width: 75%; }
|
transition: transform 350ms ease-out, opacity 350ms ease-out, box-shadow 350ms ease-out, background-color 350ms ease-out;
|
||||||
}
|
}
|
||||||
.hero-audio-progress { animation: audio-progress 15s ease-in-out infinite; }
|
.hero-flow-card.is-active {
|
||||||
.hero-audio-thumb { animation: audio-progress 15s ease-in-out infinite; }
|
transform: scale(1.05);
|
||||||
/* Mockup tilt + interactive hover — straightens on hover */
|
box-shadow: 0 0 22px rgba(0, 189, 216, 0.45), 0 0 44px rgba(0, 98, 255, 0.18);
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.hero-mockup-tilt { transform: rotate(1deg); transition: transform 500ms ease-out; }
|
|
||||||
.hero-mockup-tilt:hover { transform: rotate(0deg) scale(1.01); }
|
|
||||||
}
|
}
|
||||||
/* Glow orbs decorative float — uses tc-float-y from tailwind.config */
|
.hero-flow-card.is-inactive { opacity: 0.45; }
|
||||||
.hero-mockup-glow-a { animation: tc-float-y 6s ease-in-out infinite; }
|
/* Animated arrow draw between cards */
|
||||||
.hero-mockup-glow-b { animation: tc-float-y 8s ease-in-out infinite reverse; }
|
@keyframes hero-arrow-draw {
|
||||||
/* Reduced-motion: freeze all hero mockup animations on first frame */
|
0% { stroke-dashoffset: 24; opacity: 0.3; }
|
||||||
|
50% { stroke-dashoffset: 0; opacity: 1; }
|
||||||
|
100% { stroke-dashoffset: -24; opacity: 0.3; }
|
||||||
|
}
|
||||||
|
.hero-flow-arrow path { stroke-dasharray: 24; animation: hero-arrow-draw 1.8s ease-in-out infinite; }
|
||||||
|
/* ── Magnetic CTA — Alpine writes :style="--mx,--my", we apply it ── */
|
||||||
|
.hero-magnetic { transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); will-change: transform; }
|
||||||
|
.hero-magnetic:hover { transform: translate(var(--mx, 0px), var(--my, 0px)); }
|
||||||
|
/* Pulse halo around primary CTA */
|
||||||
|
@keyframes hero-cta-halo {
|
||||||
|
0%, 100% { transform: scale(1); opacity: 0.35; }
|
||||||
|
50% { transform: scale(1.18); opacity: 0.10; }
|
||||||
|
}
|
||||||
|
.hero-cta-halo { animation: hero-cta-halo 2.4s ease-in-out infinite; }
|
||||||
|
/* ── Parallax orb — Alpine writes :style="--ox,--oy" ── */
|
||||||
|
.hero-parallax-orb {
|
||||||
|
transform: translate3d(var(--ox, 0px), var(--oy, 0px), 0);
|
||||||
|
transition: transform 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
/* Slow rotating mesh ring overlay */
|
||||||
|
@keyframes hero-orb-rotate {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
.hero-orb-ring-a { animation: hero-orb-rotate 28s linear infinite; }
|
||||||
|
.hero-orb-ring-b { animation: hero-orb-rotate 40s linear infinite reverse; }
|
||||||
|
/* ── Shockwave click effect ── */
|
||||||
|
@keyframes hero-shockwave {
|
||||||
|
0% { transform: translate(-50%, -50%) scale(0); opacity: 0.55; }
|
||||||
|
100% { transform: translate(-50%, -50%) scale(4.5); opacity: 0; }
|
||||||
|
}
|
||||||
|
.hero-shockwave {
|
||||||
|
position: fixed;
|
||||||
|
width: 30vmax;
|
||||||
|
height: 30vmax;
|
||||||
|
border-radius: 9999px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999;
|
||||||
|
background: radial-gradient(circle, rgba(0,189,216,0.18) 0%, rgba(0,98,255,0.10) 50%, transparent 70%);
|
||||||
|
animation: hero-shockwave 700ms cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
/* ── Word-staggered title reveal ── */
|
||||||
|
@keyframes hero-word-rise {
|
||||||
|
from { opacity: 0; transform: translateY(8px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
.hero-h1-word {
|
||||||
|
display: inline-block;
|
||||||
|
opacity: 0;
|
||||||
|
animation: hero-word-rise 0.55s ease-out forwards;
|
||||||
|
}
|
||||||
|
/* ── Reduced-motion : freeze all hero animations ── */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.hero-audio-progress, .hero-audio-thumb,
|
.hero-flow-card, .hero-flow-arrow path, .hero-magnetic,
|
||||||
.hero-mockup-glow-a, .hero-mockup-glow-b {
|
.hero-cta-halo, .hero-parallax-orb, .hero-orb-ring-a, .hero-orb-ring-b,
|
||||||
|
.hero-shockwave, .hero-h1-word {
|
||||||
animation: none !important;
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
}
|
}
|
||||||
.hero-audio-progress { width: 60% !important; }
|
.hero-flow-card.is-active { transform: none !important; box-shadow: none !important; }
|
||||||
.hero-audio-thumb { left: 60% !important; }
|
.hero-flow-card.is-inactive { opacity: 1 !important; }
|
||||||
.hero-mockup-tilt { transform: none !important; transition: none !important; }
|
.hero-h1-word { opacity: 1 !important; transform: none !important; }
|
||||||
|
.hero-parallax-orb { transform: none !important; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<section class="relative overflow-hidden bg-brand-navy text-white py-24 md:py-32" aria-labelledby="hero-title">
|
<section
|
||||||
|
class="relative overflow-hidden bg-brand-navy text-white py-24 md:py-32 min-h-[85vh] flex items-center"
|
||||||
|
aria-labelledby="hero-title"
|
||||||
|
x-data="{
|
||||||
|
flowIdx: 0,
|
||||||
|
flowTimer: null,
|
||||||
|
mx: 0, my: 0,
|
||||||
|
ox: 0, oy: 0,
|
||||||
|
shockwaves: [],
|
||||||
|
swId: 0,
|
||||||
|
startFlow() {
|
||||||
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||||
|
if (this.flowTimer) clearInterval(this.flowTimer);
|
||||||
|
this.flowTimer = setInterval(() => { this.flowIdx = (this.flowIdx + 1) % 3 }, 1800);
|
||||||
|
},
|
||||||
|
onSectionMove($event) {
|
||||||
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||||
|
const r = $event.currentTarget.getBoundingClientRect();
|
||||||
|
const cx = r.left + r.width / 2;
|
||||||
|
const cy = r.top + r.height / 2;
|
||||||
|
this.ox = ($event.clientX - cx) * -0.025;
|
||||||
|
this.oy = ($event.clientY - cy) * -0.018;
|
||||||
|
},
|
||||||
|
onSectionLeave() { this.ox = 0; this.oy = 0; this.mx = 0; this.my = 0; },
|
||||||
|
onCtaMove($event) {
|
||||||
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||||
|
const r = $event.currentTarget.getBoundingClientRect();
|
||||||
|
const dx = $event.clientX - (r.left + r.width / 2);
|
||||||
|
const dy = $event.clientY - (r.top + r.height / 2);
|
||||||
|
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||||
|
const max = 110;
|
||||||
|
if (dist < max) {
|
||||||
|
const f = (1 - dist / max) * 0.18;
|
||||||
|
this.mx = Math.max(-8, Math.min(8, dx * f));
|
||||||
|
this.my = Math.max(-8, Math.min(8, dy * f));
|
||||||
|
} else { this.mx = 0; this.my = 0; }
|
||||||
|
},
|
||||||
|
onCtaLeave() { this.mx = 0; this.my = 0; },
|
||||||
|
onCtaClick($event) {
|
||||||
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||||
|
const id = ++this.swId;
|
||||||
|
this.shockwaves.push({ id, x: $event.clientX, y: $event.clientY });
|
||||||
|
setTimeout(() => { this.shockwaves = this.shockwaves.filter(s => s.id !== id); }, 700);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
x-init="startFlow()"
|
||||||
|
@mousemove="onSectionMove($event)"
|
||||||
|
@mouseleave="onSectionLeave()"
|
||||||
|
>
|
||||||
|
{# Shockwave overlays — fixed-position ripples on CTA click #}
|
||||||
|
<template x-for="sw in shockwaves" :key="sw.id">
|
||||||
|
<span class="hero-shockwave" :style="`left:${sw.x}px; top:${sw.y}px;`" aria-hidden="true"></span>
|
||||||
|
</template>
|
||||||
|
|
||||||
{# Cosmic orbs background — 3 radial gradients (blue 16%, cyan 7%, green 11%) + subtle grid + horizontal accent line #}
|
{# Cosmic orbs background — 3 radial gradients (blue 16%, cyan 7%, green 11%) + subtle grid + horizontal accent line #}
|
||||||
<div class="absolute inset-0 pointer-events-none" aria-hidden="true">
|
<div class="absolute inset-0 pointer-events-none" aria-hidden="true">
|
||||||
{# Orb 1 — primary blue, top-left #}
|
{# Orb 1 — primary blue, top-left #}
|
||||||
@@ -52,278 +163,154 @@
|
|||||||
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>
|
||||||
|
|
||||||
<div class="relative max-w-[1200px] mx-auto px-6">
|
{# 3D abstract orb — reacts to mouse via Alpine ox/oy → CSS variables #}
|
||||||
{# 2-col grid on lg+: text left, app mockup right. On <lg, mockup hidden, text centered as before. #}
|
<div
|
||||||
<div class="grid lg:grid-cols-[1fr_minmax(0,560px)] gap-12 lg:gap-16 items-center">
|
class="hero-parallax-orb absolute right-[6%] top-[12%] pointer-events-none hidden lg:block"
|
||||||
{# ---------- COLUMN LEFT: hero copy ---------- #}
|
:style="`--ox:${ox}px; --oy:${oy}px;`"
|
||||||
<div class="text-center lg:text-left max-w-3xl mx-auto lg:mx-0">
|
aria-hidden="true"
|
||||||
{# Eyebrow with gradient text and 0.18em tracking #}
|
>
|
||||||
<p class="eyebrow grad-text mb-6 animate-tc-fade-in-up" style="animation-delay: 0ms; animation-fill-mode: backwards;">
|
<div class="relative w-72 h-72">
|
||||||
TRANSCRIPTION IA · CONFORME LOI 25 · QUÉBEC
|
{# Outer ring #}
|
||||||
</p>
|
<div class="absolute inset-0 rounded-full border border-brand-b3/15" style="transform: scale(1.35);"></div>
|
||||||
|
{# Ambient blob #}
|
||||||
|
<div class="absolute inset-0 rounded-full"
|
||||||
|
style="background: radial-gradient(ellipse at 40% 40%, rgba(107,159,255,0.14) 0%, rgba(0,189,216,0.07) 50%, transparent 75%); filter: blur(20px);"></div>
|
||||||
|
{# Rotating mesh ring A #}
|
||||||
|
<div class="hero-orb-ring-a absolute inset-0 rounded-full border border-dashed border-white/[0.07]" style="transform: scale(1.15);"></div>
|
||||||
|
{# Rotating mesh ring B #}
|
||||||
|
<div class="hero-orb-ring-b absolute inset-0 rounded-full border border-white/[0.05]" style="transform: scale(0.9);"></div>
|
||||||
|
{# Inner glow core #}
|
||||||
|
<div class="absolute inset-[30%] rounded-full"
|
||||||
|
style="background: radial-gradient(circle, rgba(107,159,255,0.25) 0%, rgba(0,189,216,0.08) 60%, transparent 100%);"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{# H1 — clamp typography + grad-text accent on key phrase #}
|
<div class="relative max-w-[1200px] mx-auto px-6 w-full">
|
||||||
<h1 id="hero-title" class="text-[clamp(2.5rem,4vw,4rem)] font-black leading-[1.05] mb-6 animate-tc-fade-in-up" style="animation-delay: 75ms; animation-fill-mode: backwards;">
|
{# Single-column hero — texte centré (lg : aligné gauche). Le visuel canonique est le 3-step flow inline (pas de mockup app). #}
|
||||||
Transcrivez vos réunions
|
<div class="max-w-4xl mx-auto lg:mx-0">
|
||||||
<span class="grad-text">sans risquer votre permis</span>.
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{# Sub-headline — ≤25 words, value prop #}
|
{# Eyebrow / breadcrumb back-link "Toutes les solutions" — link to anchor #solutions sur landing #}
|
||||||
<p class="text-lg text-white/70 max-w-xl mx-auto lg:mx-0 mb-10 animate-tc-fade-in-up" style="animation-delay: 150ms; animation-fill-mode: backwards;">
|
<a href="#tarifs"
|
||||||
DictIA convertit vos audio en texte, résumé et points d'action — 100% au Québec, conforme Barreau, CPA Québec, ChAD et 6 autres ordres professionnels.
|
class="inline-flex items-center gap-2 text-white/40 hover:text-white text-xs font-sans uppercase tracking-[0.18em] transition-colors mb-8 animate-tc-fade-in-up"
|
||||||
</p>
|
style="animation-delay: 0ms; animation-fill-mode: backwards;">
|
||||||
|
<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="M19 12H5M12 19l-7-7 7-7"/></svg>
|
||||||
|
Toutes les solutions
|
||||||
|
</a>
|
||||||
|
|
||||||
{# Dual CTA — primary (demo) + secondary ghost (pricing) #}
|
{# Sous-eyebrow — pillars #}
|
||||||
<div class="flex flex-col sm:flex-row gap-3 justify-center lg:justify-start animate-tc-fade-in-up" style="animation-delay: 300ms; animation-fill-mode: backwards;">
|
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 mb-5 animate-tc-fade-in-up" style="animation-delay: 75ms; animation-fill-mode: backwards;">
|
||||||
{% from 'macros/button.html' import button %}
|
<span class="eyebrow grad-text">
|
||||||
{{ button('Réserver une démo', href='/contact', variant='primary', size='lg') }}
|
TRANSCRIPTION IA · CONFORME LOI 25 · QUÉBEC
|
||||||
{{ 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>') }}
|
</span>
|
||||||
</div>
|
<span class="hidden sm:block h-px bg-white/10 w-[80px]" aria-hidden="true"></span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-white/60 mb-8 animate-tc-fade-in-up" style="animation-delay: 75ms; animation-fill-mode: backwards;">
|
||||||
|
Audio → Texte · Résumés IA · Conforme Loi 25 & ordres professionnels
|
||||||
|
</p>
|
||||||
|
|
||||||
{# Social proof microcopy — defensible: refers to pre-launch waitlist + factual ordres pros count #}
|
{# Brand wordmark large — Dict + IA accent #}
|
||||||
<p class="mt-8 text-sm text-white/70 flex flex-wrap items-center justify-center lg:justify-start gap-x-2 gap-y-1 animate-tc-fade-in-up" style="animation-delay: 400ms; animation-fill-mode: backwards;">
|
<h1 id="hero-title" class="font-black leading-[0.92] mb-6 text-white" style="font-size: clamp(3rem, 7vw, 6rem); letter-spacing: -0.025em;">
|
||||||
<span class="inline-flex items-center gap-1.5">
|
<span class="hero-h1-word" style="animation-delay: 80ms;">Dict</span><span class="hero-h1-word grad-text" style="animation-delay: 160ms;">IA</span>
|
||||||
<svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor" class="text-brand-b3" aria-hidden="true">
|
</h1>
|
||||||
<path d="M10 2L3 5v5.5c0 4.04 2.84 7.85 7 8.5 4.16-.65 7-4.46 7-8.5V5l-7-3z"/>
|
|
||||||
</svg>
|
{# 3-step flow inline — REMPLACE le mockup app actuel. Auto-cycle 1→2→3 toutes les 1.8s. #}
|
||||||
<span>Conçu avec 9 ordres professionnels québécois</span>
|
<div
|
||||||
</span>
|
class="flex flex-wrap items-center gap-2 mb-8 animate-tc-fade-in-up"
|
||||||
<span class="text-white/30">·</span>
|
style="animation-delay: 200ms; animation-fill-mode: backwards;"
|
||||||
<span>Pré-inscription ouverte</span>
|
role="group"
|
||||||
<span class="text-white/30">·</span>
|
aria-label="Flux DictIA en 3 étapes"
|
||||||
<span>Lancement printemps 2026</span>
|
>
|
||||||
</p>
|
{% set flow_steps = [
|
||||||
|
('Importez un fichier', '<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="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>'),
|
||||||
|
('Texte en 2 min', '<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 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="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/></svg>'),
|
||||||
|
('Résumé + actions', '<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="M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5z"/><path d="M5 19l1.5-1.5"/><path d="M19 19l-1.5-1.5"/></svg>')
|
||||||
|
] %}
|
||||||
|
{% for label, icon in flow_steps %}
|
||||||
|
<span
|
||||||
|
class="hero-flow-card inline-flex items-center gap-1.5 border border-white/10 bg-white/[0.04] rounded-full px-3.5 py-1.5 text-[12px] font-medium text-white/70"
|
||||||
|
:class="flowIdx === {{ loop.index0 }} ? 'is-active grad-bg !text-white !border-transparent' : 'is-inactive'"
|
||||||
|
aria-label="Étape {{ loop.index }} sur 3 : {{ label | striptags }}"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">{{ icon | safe }}</span>
|
||||||
|
<span>{{ label | safe }}</span>
|
||||||
|
</span>
|
||||||
|
{% if not loop.last %}
|
||||||
|
<svg class="hero-flow-arrow w-5 h-5 text-white/40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="M5 12h14M13 6l6 6-6 6"/>
|
||||||
|
</svg>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# ---------- COLUMN RIGHT: app mockup (lg+ only) ---------- #}
|
{# H2 (canonique : phrase clé en cyan/grad) #}
|
||||||
<div class="hidden lg:block relative animate-tc-fade-in-up" style="animation-delay: 250ms; animation-fill-mode: backwards;">
|
<p class="text-xl md:text-2xl lg:text-3xl font-black mb-6 leading-snug grad-text animate-tc-fade-in-up" style="animation-delay: 280ms; animation-fill-mode: backwards;">
|
||||||
{# Decorative glow orbs floating behind the mockup #}
|
Transcription IA locale en 2 minutes — conforme Barreau, CPA Québec et ChAD.
|
||||||
<div class="absolute -inset-8 pointer-events-none" aria-hidden="true">
|
</p>
|
||||||
<div class="hero-mockup-glow-a absolute -top-10 -left-10 w-72 h-72 rounded-full bg-brand-b1/15 blur-3xl"></div>
|
|
||||||
<div class="hero-mockup-glow-b absolute -bottom-10 -right-6 w-64 h-64 rounded-full bg-brand-b3/10 blur-3xl"></div>
|
{# Sub canonique #}
|
||||||
|
<p class="text-base md:text-lg text-white/65 max-w-3xl leading-relaxed mb-10 animate-tc-fade-in-up" style="animation-delay: 360ms; animation-fill-mode: backwards;">
|
||||||
|
DictIA transforme vos réunions en texte, résumés et points d'action en 2 minutes — hébergé au Québec sur OVH Beauharnois, zéro Cloud Act. Contrairement à Teams Copilot ou Otter.ai, vos données ne quittent jamais le Québec. Conforme au Cadre IA MCN et aux 5 ordres à directives IA formelles (Barreau, ChAD, CMQ, OIIQ, OACIQ).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{# Stats grid — 4 colonnes #}
|
||||||
|
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-10 max-w-3xl animate-tc-fade-in-up" style="animation-delay: 440ms; animation-fill-mode: backwards;">
|
||||||
|
{% for stat in [
|
||||||
|
('~2 min', 'Pour 1 h d\'audio'),
|
||||||
|
('5 ordres','Directives IA formelles'),
|
||||||
|
('95 %+', 'Précision FR-CA'),
|
||||||
|
('0 $', 'Frais par utilisateur')
|
||||||
|
] %}
|
||||||
|
<div class="bg-white/[0.04] border border-white/10 rounded px-4 py-3 text-center">
|
||||||
|
<div class="font-display font-black grad-text text-xl leading-none mb-1">{{ stat[0] | safe }}</div>
|
||||||
|
<div class="text-[10.5px] text-white/55 uppercase tracking-wider leading-tight">{{ stat[1] | safe }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{# The mockup itself — role=img with descriptive label for screen readers #}
|
{# Dual CTA — primary "Réserver une démo" magnétique + ghost "Voir les forfaits" #}
|
||||||
<div role="img"
|
<div class="flex flex-col sm:flex-row gap-3 animate-tc-fade-in-up" style="animation-delay: 520ms; animation-fill-mode: backwards;">
|
||||||
aria-label="Aperçu de l'interface DictIA — liste d'enregistrements à gauche, transcription au centre, résumé IA à droite"
|
{# Magnetic CTA wrapper — Alpine writes mx/my via mousemove #}
|
||||||
class="hero-mockup-tilt relative bg-brand-navy2 border border-white/[0.08] shadow-2xl shadow-black/40 rounded overflow-hidden">
|
<span class="relative inline-flex">
|
||||||
|
{# Pulse halo behind the primary CTA #}
|
||||||
|
<span class="hero-cta-halo absolute -inset-1 rounded pointer-events-none"
|
||||||
|
style="background: radial-gradient(ellipse, rgba(107,159,255,0.50) 0%, transparent 70%);"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
<a
|
||||||
|
href="/contact"
|
||||||
|
@mousemove="onCtaMove($event)"
|
||||||
|
@mouseleave="onCtaLeave()"
|
||||||
|
@click="onCtaClick($event)"
|
||||||
|
class="hero-magnetic relative inline-flex items-center justify-center gap-2 bg-gradient-to-r from-brand-b3 to-brand-b1 hover:from-brand-b1 hover:to-brand-b3 text-brand-navy font-bold px-8 h-12 text-sm rounded transition-colors w-full sm:w-auto focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2"
|
||||||
|
:style="`--mx:${mx}px; --my:${my}px;`"
|
||||||
|
>
|
||||||
|
<span>Réserver une démo</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" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="/tarifs"
|
||||||
|
class="inline-flex items-center justify-center gap-2 border border-white/15 hover:border-white/30 text-white font-semibold px-8 h-12 text-sm rounded transition-colors w-full sm:w-auto focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2"
|
||||||
|
>
|
||||||
|
<span>Voir les forfaits</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="M6 9l6 6 6-6"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{# A) Browser chrome / window header (h-8) #}
|
{# Social proof microcopy — defensible: 9 ordres pros + waitlist + launch date (préservé pour SEO/legal) #}
|
||||||
<div class="flex items-center px-3 h-8 border-b border-white/[0.06] bg-white/[0.03]" aria-hidden="true">
|
<p class="mt-8 text-sm text-white/70 flex flex-wrap items-center gap-x-2 gap-y-1 animate-tc-fade-in-up" style="animation-delay: 600ms; animation-fill-mode: backwards;">
|
||||||
{# Traffic light dots #}
|
<span class="inline-flex items-center gap-1.5">
|
||||||
<div class="flex items-center gap-1.5">
|
<svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor" class="text-brand-b3" aria-hidden="true">
|
||||||
<span class="block w-3 h-3 rounded-full" style="background:#ff5f57"></span>
|
<path d="M10 2L3 5v5.5c0 4.04 2.84 7.85 7 8.5 4.16-.65 7-4.46 7-8.5V5l-7-3z"/>
|
||||||
<span class="block w-3 h-3 rounded-full" style="background:#febc2e"></span>
|
</svg>
|
||||||
<span class="block w-3 h-3 rounded-full" style="background:#28c840"></span>
|
<span>Conçu avec 9 ordres professionnels québécois</span>
|
||||||
</div>
|
</span>
|
||||||
{# Centered tab label #}
|
<span class="text-white/30">·</span>
|
||||||
<div class="flex-1 text-center text-[11px] font-medium text-white/60 tracking-tight">DictIA — Enquêter</div>
|
<span>Pré-inscription ouverte</span>
|
||||||
{# Expand icon #}
|
<span class="text-white/30">·</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 text-white/40">
|
<span>Lancement printemps 2026</span>
|
||||||
<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/>
|
</p>
|
||||||
</svg>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{# B) Main app area — 3 sub-columns (sidebar / center / summary), h-[460px] #}
|
|
||||||
<div class="grid grid-cols-[180px_1fr_170px] h-[460px]"
|
|
||||||
x-data="{ idx: 0 }"
|
|
||||||
x-init="$el.__hero_int = setInterval(() => { idx = (idx + 1) % 5 }, 2800)">
|
|
||||||
|
|
||||||
{# B1 — SIDEBAR (left) #}
|
|
||||||
<aside class="border-r border-white/[0.06] flex flex-col overflow-hidden">
|
|
||||||
{# Sidebar header — DictIA monogram + JD avatar #}
|
|
||||||
<div class="flex items-center justify-between px-3 py-2.5 border-b border-white/[0.05]">
|
|
||||||
<span class="grad-text font-black text-[13px] tracking-tight">DictIA</span>
|
|
||||||
<span class="grad-bg rounded-full w-6 h-6 flex items-center justify-center text-[10px] font-bold text-white">JD</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# New recording button — rounded-none brutalist #}
|
|
||||||
<div class="px-2 pt-2">
|
|
||||||
<button type="button" class="grad-bg w-full inline-flex items-center justify-center gap-1.5 py-2 text-[11px] font-semibold rounded-none" tabindex="-1" 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-3 h-3"><path d="M12 5v14M5 12h14"/></svg>
|
|
||||||
<span>Nouvel Enregistrement</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Search input #}
|
|
||||||
<div class="px-2 pt-2">
|
|
||||||
<div class="relative">
|
|
||||||
<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="absolute left-2 top-1/2 -translate-y-1/2 w-3 h-3 text-white/40"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
|
|
||||||
<div class="bg-white/[0.05] border border-white/[0.06] py-1.5 pl-7 pr-2 text-[10px] text-white/40 rounded-none">Rechercher…</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Sidebar list — scrollable area #}
|
|
||||||
<div class="flex-1 overflow-hidden mt-3 px-1">
|
|
||||||
|
|
||||||
{# Group: Semaine dernière #}
|
|
||||||
<p class="px-2 text-[9px] uppercase tracking-[0.12em] text-white/40 font-bold mb-1.5">Semaine dernière</p>
|
|
||||||
|
|
||||||
{# Item 1 — ACTIVE #}
|
|
||||||
<div class="px-2 py-2 mb-1 bg-white/[0.08] border-l-2 border-brand-b1">
|
|
||||||
<p class="text-[11px] text-white font-semibold truncate">Dossier Beaumont — clauses 7 et 9</p>
|
|
||||||
<p class="text-[9px] text-white/50 truncate mt-0.5">Jeu · Me Roy, Jean T., Marie C.</p>
|
|
||||||
<span class="inline-block mt-1 rounded-full bg-blue-500/20 text-blue-300 text-[9px] px-2 py-0.5 font-medium">En cours</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Item 2 #}
|
|
||||||
<div class="px-2 py-2 mb-1 hover:bg-white/[0.04]">
|
|
||||||
<p class="text-[11px] text-white/90 truncate">Succession Gagnon — répartition…</p>
|
|
||||||
<p class="text-[9px] text-white/50 truncate mt-0.5">26 févr. · Me Gagnon, Client</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Group: Mois dernier #}
|
|
||||||
<p class="px-2 mt-3 text-[9px] uppercase tracking-[0.12em] text-white/40 font-bold mb-1.5">Mois dernier</p>
|
|
||||||
|
|
||||||
{# Item 3 #}
|
|
||||||
<div class="px-2 py-2 mb-1 hover:bg-white/[0.04]">
|
|
||||||
<p class="text-[11px] text-white/90 truncate">Consultation Tremblay — litige loc…</p>
|
|
||||||
<p class="text-[9px] text-white/50 truncate mt-0.5">19 févr. · Me Roy, Sophie T.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Item 4 — Barreau Confidentiel #}
|
|
||||||
<div class="px-2 py-2 mb-1 hover:bg-white/[0.04]">
|
|
||||||
<p class="text-[11px] text-white/90 truncate">Réunion constitutive INC Bédard…</p>
|
|
||||||
<p class="text-[9px] text-white/50 truncate mt-0.5">10 févr. · Sophie B., Jean R.</p>
|
|
||||||
<span class="inline-block mt-1 rounded-full bg-purple-500/20 text-purple-300 text-[9px] px-2 py-0.5 font-medium">Barreau Confidentiel</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Item 5 — CPA Corporatif #}
|
|
||||||
<div class="px-2 py-2 mb-1 hover:bg-white/[0.04]">
|
|
||||||
<p class="text-[11px] text-white/90 truncate">Entretien RQAP et structure salariale</p>
|
|
||||||
<p class="text-[9px] text-white/50 truncate mt-0.5">10 févr. · Me Roy, Allison, Robert</p>
|
|
||||||
<span class="inline-block mt-1 rounded-full bg-emerald-500/20 text-emerald-300 text-[9px] px-2 py-0.5 font-medium">CPA Corporatif</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Item 6 — Urgent Client #}
|
|
||||||
<div class="px-2 py-2 mb-1 hover:bg-white/[0.04]">
|
|
||||||
<p class="text-[11px] text-white/90 truncate">Révision contrat Côté & Associés</p>
|
|
||||||
<p class="text-[9px] text-white/50 truncate mt-0.5">5 févr. · Me Roy, Marie C.</p>
|
|
||||||
<span class="inline-block mt-1 rounded-full bg-red-500/20 text-red-300 text-[9px] px-2 py-0.5 font-medium">Urgent Client</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
{# B2 — CENTER (audio + transcript) #}
|
|
||||||
<section class="flex flex-col p-3 overflow-hidden">
|
|
||||||
|
|
||||||
{# Header card #}
|
|
||||||
<div class="mb-3">
|
|
||||||
<h3 class="text-[13px] font-bold text-white">Dossier Beaumont — clauses 7 et 9</h3>
|
|
||||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 mt-1.5 text-[10px] text-white/50">
|
|
||||||
<span class="inline-flex items-center gap-1">
|
|
||||||
<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 h-3"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
|
||||||
<span>Me Roy, Jean T., Marie C.</span>
|
|
||||||
</span>
|
|
||||||
<span class="inline-flex items-center gap-1">
|
|
||||||
<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 h-3"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
|
|
||||||
<span>27 févr. 2026</span>
|
|
||||||
</span>
|
|
||||||
<span class="inline-flex items-center gap-1">
|
|
||||||
<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 h-3"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
|
||||||
<span>38:47</span>
|
|
||||||
</span>
|
|
||||||
<span class="inline-flex items-center gap-1">
|
|
||||||
<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 h-3"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>
|
|
||||||
<span>28.3 MB</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Audio player block #}
|
|
||||||
<div class="bg-white/[0.04] p-3 rounded-none">
|
|
||||||
{# Progress bar #}
|
|
||||||
<div class="relative h-1 bg-white/10">
|
|
||||||
<div class="hero-audio-progress h-full grad-bg" style="width:60%"></div>
|
|
||||||
<div class="hero-audio-thumb absolute -top-1 grad-bg w-3 h-3 rounded-full" style="left:60%; transform: translateX(-50%);"></div>
|
|
||||||
</div>
|
|
||||||
{# Time + speed + play row #}
|
|
||||||
<div class="flex items-center justify-between mt-2">
|
|
||||||
<div class="text-[10px] font-mono text-white/60">
|
|
||||||
<span class="text-white/80">28:17</span>
|
|
||||||
<span class="text-white/30 mx-1">/</span>
|
|
||||||
<span>46:23</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="text-[9px] text-white/60 px-1.5 py-0.5 bg-white/[0.06] rounded-full font-mono">1x</span>
|
|
||||||
<button type="button" class="grad-bg w-8 h-8 rounded-full flex items-center justify-center animate-plus-breathe" tabindex="-1" aria-hidden="true">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" class="w-3 h-3"><path d="M8 5v14l11-7z"/></svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Transcript area — 5 lines, active line cycles via Alpine idx #}
|
|
||||||
<div class="flex-1 mt-3 overflow-hidden space-y-1.5">
|
|
||||||
<div class="px-2 py-1.5 transition-colors duration-300" :class="idx === 0 ? 'bg-brand-b1/10 border-l-2 border-brand-b1' : 'border-l-2 border-transparent'">
|
|
||||||
<p class="text-[10px]">
|
|
||||||
<span class="text-brand-b1 font-bold">Allison</span>
|
|
||||||
<span class="text-white/80 ml-1.5">Tu l'as su? Comment ça va, vous deux?</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="px-2 py-1.5 transition-colors duration-300" :class="idx === 1 ? 'bg-brand-b1/10 border-l-2 border-brand-b1' : 'border-l-2 border-transparent'">
|
|
||||||
<p class="text-[10px]">
|
|
||||||
<span class="text-brand-b3 font-bold">SPEAKER_02</span>
|
|
||||||
<span class="text-white/80 ml-1.5">Super bien, super vite, mais très bien.</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="px-2 py-1.5 transition-colors duration-300" :class="idx === 2 ? 'bg-brand-b1/10 border-l-2 border-brand-b1' : 'border-l-2 border-transparent'">
|
|
||||||
<p class="text-[10px]">
|
|
||||||
<span class="text-brand-b1 font-bold">Allison</span>
|
|
||||||
<span class="text-white/80 ml-1.5">Oui, oui. Je suis contente d'entendre ça.</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="px-2 py-1.5 transition-colors duration-300" :class="idx === 3 ? 'bg-brand-b1/10 border-l-2 border-brand-b1' : 'border-l-2 border-transparent'">
|
|
||||||
<p class="text-[10px]">
|
|
||||||
<span class="text-brand-b3 font-bold">SPEAKER_02</span>
|
|
||||||
<span class="text-white/80 ml-1.5">Moi, je l'ai vu quand vous l'avez payé.</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="px-2 py-1.5 transition-colors duration-300" :class="idx === 4 ? 'bg-brand-b1/10 border-l-2 border-brand-b1' : 'border-l-2 border-transparent'">
|
|
||||||
<p class="text-[10px]">
|
|
||||||
<span class="text-brand-b1 font-bold">Allison</span>
|
|
||||||
<span class="text-white/80 ml-1.5">J'étais comme, « Hein? »</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Question input (disabled — visual only) #}
|
|
||||||
<div class="mt-3">
|
|
||||||
<input type="text" disabled tabindex="-1" aria-hidden="true"
|
|
||||||
placeholder="Posez une question sur cette transcription…"
|
|
||||||
class="w-full bg-white/[0.05] border border-white/10 px-3 py-2 text-[11px] text-white/40 placeholder-white/40 rounded-none">
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{# B3 — RIGHT pane (summary) #}
|
|
||||||
<aside class="border-l border-white/[0.06] p-3 overflow-hidden">
|
|
||||||
{# Tabs #}
|
|
||||||
<div class="flex items-center gap-3 border-b border-white/[0.06] pb-2 mb-3">
|
|
||||||
<span class="text-[10px] uppercase tracking-[0.12em] text-brand-b1 font-bold border-b-2 border-brand-b1 pb-1 -mb-[0.5rem]">Résumé</span>
|
|
||||||
<span class="text-[10px] uppercase tracking-[0.12em] text-white/40 font-medium">Notes</span>
|
|
||||||
<span class="text-[10px] uppercase tracking-[0.12em] text-white/40 font-medium">Discuter</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Summary text #}
|
|
||||||
<p class="text-[10px] text-white/80 leading-relaxed">
|
|
||||||
Cette réunion aborde les complexités fiscales et juridiques liées à la création d'un nouveau produit nommé Dictéa…
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{# Key points #}
|
|
||||||
<p class="mt-3 text-[9px] uppercase tracking-[0.12em] text-white/50 font-bold mb-1.5">Points clés</p>
|
|
||||||
<ul class="space-y-1 text-[10px] text-white/70 leading-snug">
|
|
||||||
<li class="flex gap-1.5"><span class="text-brand-b1">•</span><span>Structure de propriété : SNC + INC</span></li>
|
|
||||||
<li class="flex gap-1.5"><span class="text-brand-b1">•</span><span>RQAP et Salaire : Allison ne peut pas se verser</span></li>
|
|
||||||
<li class="flex gap-1.5"><span class="text-brand-b1">•</span><span>Subventions : 4 ans à conserver</span></li>
|
|
||||||
<li class="flex gap-1.5"><span class="text-brand-b1">•</span><span>Exonération des gains personnels</span></li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
</div>{# /Main app area #}
|
|
||||||
</div>{# /mockup container #}
|
|
||||||
</div>{# /column right #}
|
|
||||||
</div>{# /grid #}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -1075,11 +1062,14 @@
|
|||||||
<div class="text-center max-w-2xl mx-auto mb-14">
|
<div class="text-center max-w-2xl mx-auto mb-14">
|
||||||
<p class="eyebrow grad-text mb-4">COMMENT ÇA MARCHE</p>
|
<p class="eyebrow grad-text mb-4">COMMENT ÇA MARCHE</p>
|
||||||
<h2 id="pipeline-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-4 text-brand-navy">
|
<h2 id="pipeline-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-4 text-brand-navy">
|
||||||
Du fichier au résumé <span class="grad-text">en 4 étapes</span>.
|
Du fichier au résumé <span class="grad-text">— en temps réel</span>.
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-lg text-brand-navy/70">
|
<p class="text-lg text-brand-navy/70">
|
||||||
Aucune installation côté utilisateur, aucune conversion préalable. DictIA orchestre l'ensemble du pipeline — du téléversement à l'export — en moins de deux minutes pour une heure d'audio.
|
Aucune installation côté utilisateur, aucune conversion préalable. DictIA orchestre l'ensemble du pipeline — du téléversement à l'export — en moins de deux minutes pour une heure d'audio.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="text-sm text-brand-navy/55 mt-3 italic">
|
||||||
|
Survolez une fonctionnalité pour voir la machine en action. Glissez pour calculer votre gain de productivité.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Pipeline track + 4 nodes — Alpine state drives all visuals.
|
{# Pipeline track + 4 nodes — Alpine state drives all visuals.
|
||||||
@@ -1235,6 +1225,82 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{# ===== LANGUES + IA LOCALE ===== #}
|
||||||
|
{# Section compacte : grille 99+ langues détectées (gauche) + carte IA Mistral 7B LOCAL (droite). #}
|
||||||
|
<section class="bg-brand-bg py-16 border-y border-brand-border" aria-labelledby="langues-title">
|
||||||
|
<div class="max-w-[1100px] mx-auto px-6">
|
||||||
|
<div class="grid lg:grid-cols-2 gap-8">
|
||||||
|
{# Colonne gauche — 99+ langues #}
|
||||||
|
<div class="bg-white rounded border border-brand-border p-6">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<span class="grad-bg w-9 h-9 rounded flex items-center justify-center text-white" 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-5 h-5"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<h2 id="langues-title" class="text-lg font-bold text-brand-navy leading-tight">99+ langues détectées</h2>
|
||||||
|
<p class="text-xs text-brand-navy/55 mt-0.5">WhisperX Large-v3 · multilingue par défaut</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Grille codes langue — 12 puces (FR mis en avant via grad-bg) #}
|
||||||
|
<ul class="grid grid-cols-6 sm:grid-cols-8 gap-1.5 mb-4" role="list" aria-label="Quelques langues supportées">
|
||||||
|
{% for code in ['FR','EN','ES','DE','PT','IT','NL','PL','ZH','JA','KO','AR','RU','HI','TR','VI','TH','SV','DA','NO','FI'] %}
|
||||||
|
<li class="text-center">
|
||||||
|
<span class="block text-[10px] font-mono font-bold py-1 rounded
|
||||||
|
{{ 'grad-bg text-white' if code == 'FR' else 'bg-brand-bg border border-brand-border text-brand-navy/70' }}">
|
||||||
|
{{ code }}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p class="text-xs text-brand-navy/55 flex items-center gap-1.5">
|
||||||
|
<span class="inline-block w-1.5 h-1.5 rounded-full bg-brand-b3"></span>
|
||||||
|
<span><strong class="text-brand-navy">Auto</strong> · Détection automatique de la langue à l'upload</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Colonne droite — IA intégrée Mistral 7B LOCAL #}
|
||||||
|
<div class="bg-brand-navy text-white rounded p-6 relative overflow-hidden">
|
||||||
|
{# Subtle orb décoratif #}
|
||||||
|
<div class="absolute -top-12 -right-12 w-48 h-48 rounded-full pointer-events-none" aria-hidden="true"
|
||||||
|
style="background: radial-gradient(circle, rgba(0,189,216,0.15) 0%, transparent 60%); filter: blur(30px);"></div>
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<span class="bg-white/10 border border-white/15 w-9 h-9 rounded flex items-center justify-center text-brand-b3" 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-5 h-5"><path d="M12 2a3 3 0 0 0-3 3v1a3 3 0 0 0-3 3v1a3 3 0 0 0 0 6v1a3 3 0 0 0 3 3 3 3 0 0 0 6 0 3 3 0 0 0 3-3v-1a3 3 0 0 0 0-6V9a3 3 0 0 0-3-3V5a3 3 0 0 0-3-3z"/></svg>
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold leading-tight">IA intégrée — Mistral 7B <span class="grad-text">(LOCAL)</span></h3>
|
||||||
|
<p class="text-xs text-white/55 mt-0.5">Inférence sur votre GPU · zéro cloud étranger</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="space-y-2 text-sm" role="list">
|
||||||
|
<li class="flex items-start gap-2">
|
||||||
|
<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 text-brand-b3 shrink-0" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>
|
||||||
|
<span class="text-white/80"><strong class="text-white">Résumé · Points d'action · Q&R</strong></span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-start gap-2">
|
||||||
|
<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 text-brand-b3 shrink-0" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>
|
||||||
|
<span class="text-white/80">Données hébergées sur <strong class="text-white">vos serveurs</strong> · jamais partagées</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-start gap-2">
|
||||||
|
<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 text-brand-b3 shrink-0" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>
|
||||||
|
<span class="text-white/80">Zéro connexion <strong class="text-white">OpenAI · Google · Microsoft</strong></span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-start gap-2">
|
||||||
|
<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 text-brand-b3 shrink-0" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>
|
||||||
|
<span class="text-white/80">Inférence <strong class="text-white">hors-ligne</strong> · résultats en secondes</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{# ===== BENTO FEATURES ===== #}
|
{# ===== BENTO FEATURES ===== #}
|
||||||
<section class="bg-white py-20" aria-labelledby="bento-title">
|
<section class="bg-white py-20" aria-labelledby="bento-title">
|
||||||
<div class="max-w-[1060px] mx-auto px-6">
|
<div class="max-w-[1060px] mx-auto px-6">
|
||||||
@@ -1472,6 +1538,10 @@
|
|||||||
|
|
||||||
{% include 'marketing/_partials/_pricing_tiers.html' %}
|
{% include 'marketing/_partials/_pricing_tiers.html' %}
|
||||||
|
|
||||||
|
<p class="text-center text-xs text-brand-navy/60 mt-6 max-w-2xl mx-auto">
|
||||||
|
Tous les prix en CAD, taxes en sus (TPS 5 % + TVQ 9,975 %).
|
||||||
|
</p>
|
||||||
|
|
||||||
{# ROI CALCULATOR — Alpine.js, hypotheses transparentes pour LPC art. 219 hygiene #}
|
{# ROI CALCULATOR — Alpine.js, hypotheses transparentes pour LPC art. 219 hygiene #}
|
||||||
<div x-data="roiCalculator()" class="mt-16 max-w-3xl mx-auto bg-white p-8 rounded border border-brand-border" aria-labelledby="roi-title">
|
<div x-data="roiCalculator()" class="mt-16 max-w-3xl mx-auto bg-white p-8 rounded border border-brand-border" aria-labelledby="roi-title">
|
||||||
<p class="eyebrow text-center grad-text mb-2">CALCULATEUR ROI</p>
|
<p class="eyebrow text-center grad-text mb-2">CALCULATEUR ROI</p>
|
||||||
@@ -1633,8 +1703,22 @@
|
|||||||
Architecture <span class="grad-text">conçue avec</span> les exigences professionnelles québécoises.
|
Architecture <span class="grad-text">conçue avec</span> les exigences professionnelles québécoises.
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-lg text-white/70">
|
<p class="text-lg text-white/70">
|
||||||
DictIA mappe son architecture aux cadres réglementaires applicables au secteur public et aux ordres professionnels du Québec. Détails techniques (EFVP, audit trail, déclaration CAI) disponibles sur demande : <a href="mailto:info@dictia.ca" class="grad-text font-semibold hover:underline">info@dictia.ca</a>.
|
DictIA mappe son architecture aux cadres réglementaires applicables au secteur public et aux ordres professionnels du Québec. DictIA a été conçu pour les secteurs réglementés du Québec — Loi 25, Cloud Act, Barreau, ChAD, AMF. Détails techniques (EFVP, audit trail, déclaration CAI) disponibles sur demande : <a href="mailto:info@dictia.ca" class="grad-text font-semibold hover:underline">info@dictia.ca</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{# Chips claims — 3 marqueurs canoniques (~192 000 pros · 5 ordres · 0 donnée hors-Québec) #}
|
||||||
|
<div class="flex flex-wrap items-center justify-center gap-2 mt-6" role="list" aria-label="Chiffres clés conformité">
|
||||||
|
{% for chip in [
|
||||||
|
('~192 000 professionnels Tier 1', '<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="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>'),
|
||||||
|
('5 ordres · directives IA formelles', '<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="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>'),
|
||||||
|
('0 donnée transmise hors Québec', '<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="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>')
|
||||||
|
] %}
|
||||||
|
<span class="inline-flex items-center gap-1.5 bg-white/[0.06] border border-white/15 rounded-full px-3 py-1 text-xs text-white/85" role="listitem">
|
||||||
|
<span class="text-brand-b3" aria-hidden="true">{{ chip[1] | safe }}</span>
|
||||||
|
<span>{{ chip[0] | safe }}</span>
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</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).
|
||||||
@@ -1910,6 +1994,140 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{# ===== CÉGEPS · CONFORMITÉ AU 19 JUIN 2026 (spotlight) ===== #}
|
||||||
|
{# Source canonique : texte fourni par l'utilisateur — Cadre IA MCN, Énoncé de principes mis à jour
|
||||||
|
+ Indication d'application IAG (publié 19 déc. 2025, conformité 19 juin 2026, art. 21 LGGRI). #}
|
||||||
|
<style>
|
||||||
|
/* Pulse glow autour du badge "Conformité imminente" */
|
||||||
|
@keyframes cegep-pulse-badge {
|
||||||
|
0%, 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.55); }
|
||||||
|
50% { box-shadow: 0 0 0 6px rgba(245, 158, 11, 0); }
|
||||||
|
}
|
||||||
|
.cegep-badge-pulse { animation: cegep-pulse-badge 2.2s ease-in-out infinite; }
|
||||||
|
/* Bullets reveal staggered (intersection observer via Alpine x-intersect non requis, on utilise CSS animation-delay) */
|
||||||
|
@keyframes cegep-bullet-rise {
|
||||||
|
from { opacity: 0; transform: translateY(8px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
.cegep-bullet { opacity: 0; animation: cegep-bullet-rise 0.5s ease-out forwards; }
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.cegep-badge-pulse, .cegep-bullet { animation: none !important; }
|
||||||
|
.cegep-bullet { opacity: 1 !important; transform: none !important; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<section class="bg-brand-bg py-20 border-y border-brand-border" aria-labelledby="cegeps-title">
|
||||||
|
<div class="max-w-[1100px] mx-auto px-6">
|
||||||
|
|
||||||
|
{# Bandeau d'eyebrow + badge pulse #}
|
||||||
|
<div class="flex flex-wrap items-center justify-center gap-3 mb-5">
|
||||||
|
<span class="cegep-badge-pulse inline-flex items-center gap-1.5 bg-amber-50 border border-amber-300 text-amber-800 rounded-full px-3 py-1 text-[11px] font-bold uppercase tracking-wider">
|
||||||
|
<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-3 h-3" aria-hidden="true"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||||
|
Conformité imminente
|
||||||
|
</span>
|
||||||
|
<span class="text-xs uppercase tracking-[0.18em] text-brand-navy/55 font-semibold">
|
||||||
|
Adopté 19 déc. 2025 · Décret officiel
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center max-w-3xl mx-auto mb-12">
|
||||||
|
<h2 id="cegeps-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-5 text-brand-navy leading-tight">
|
||||||
|
Conformité au <span class="grad-text">19 juin 2026</span> — vous dirigez un cégep, un CISSS ou un ministère ?
|
||||||
|
</h2>
|
||||||
|
<p class="text-lg text-brand-navy/80 font-semibold mb-3">
|
||||||
|
Vous avez jusqu'au 19 juin 2026.
|
||||||
|
</p>
|
||||||
|
<p class="text-base text-brand-navy/70 leading-relaxed">
|
||||||
|
Depuis le 19 décembre 2025, tous les organismes publics québécois doivent appliquer un cadre IA strict. Aucun outil cloud non approuvé ne peut recevoir de renseignements confidentiels — fini ChatGPT ou Teams Copilot dans les CA, les séances cliniques ou les comités universitaires.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Carte spotlight — Cadre IA MCN détaillé #}
|
||||||
|
<div class="bg-white border border-brand-border rounded p-6 md:p-8 max-w-4xl mx-auto">
|
||||||
|
<div class="flex items-start gap-3 mb-5">
|
||||||
|
<span class="grad-bg w-10 h-10 rounded flex items-center justify-center text-white shrink-0" 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-5 h-5"><path d="M3 21h18"/><path d="M5 21V8l7-4 7 4v13"/><path d="M9 21v-6h6v6"/></svg>
|
||||||
|
</span>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<h3 class="text-lg md:text-xl font-bold text-brand-navy leading-tight">
|
||||||
|
Cadre IA MCN — Énoncé de principes mis à jour + Indication d'application IAG
|
||||||
|
</h3>
|
||||||
|
<p class="text-xs text-brand-navy/55 mt-1">Publié le 19 décembre 2025 sous l'art. 21 LGGRI</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-sm text-brand-navy/75 leading-relaxed mb-5">
|
||||||
|
Interdit aux organismes publics (art. 2 LGGRI) d'entrer des renseignements confidentiels dans un système IA non approuvé — ministères, organismes budgétaires, Santé Québec, CISSS/CIUSSS, centres de services scolaires, cégeps, universités. Régime allégé pour entreprises du gouvernement (art. 4 — Hydro-Québec, SAQ, Loto-Québec, CDPQ).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul class="space-y-2.5 mb-6" role="list">
|
||||||
|
{% for bullet in [
|
||||||
|
('Énoncé de principes — 12 principes éthiques applicables à tout système IA dans l\'administration publique', 0),
|
||||||
|
('Indication d\'application IAG — gouvernance, gestion des risques, mesures de contrôle, protection des données, formation du personnel', 80),
|
||||||
|
('Délai conformité : <strong>19 juin 2026</strong> (6 mois post publication 19 déc. 2025)', 160),
|
||||||
|
('Municipalités, MRC et Assemblée nationale <em>non</em> visées par l\'Énoncé — mais Loi sur l\'accès (A-2.1) reste applicable aux séances publiques', 240),
|
||||||
|
('<strong>Loi 25</strong> — voix = donnée biométrique (LCCJTI art. 44-45), déclaration CAI obligatoire si banque biométrique', 320),
|
||||||
|
('<strong>Loi 96 (C-11)</strong> — documents générés en français pour organisations 25+ employés', 400),
|
||||||
|
('Hébergement au Québec — aucune société US dans la chaîne (Cloud Act inapplicable)', 480)
|
||||||
|
] %}
|
||||||
|
<li class="cegep-bullet flex items-start gap-2.5 text-sm text-brand-navy/80 leading-relaxed" role="listitem"
|
||||||
|
style="animation-delay: {{ bullet[1] }}ms;">
|
||||||
|
<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-1 text-brand-b3 shrink-0" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>
|
||||||
|
<span>{{ bullet[0] | safe }}</span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{# Liste organismes visés (chips) #}
|
||||||
|
<div class="border-t border-brand-border pt-5">
|
||||||
|
<p class="text-[11px] uppercase tracking-[0.16em] font-bold text-brand-navy/50 mb-3">Organismes visés par le Cadre IA MCN</p>
|
||||||
|
<div class="flex flex-wrap gap-1.5" role="list">
|
||||||
|
{% for org in ['Ministères', 'Santé Québec', 'CISSS/CIUSSS', 'Universités', 'Cégeps', 'Hydro-Québec', 'SAQ', 'SAAQ', 'CDPQ'] %}
|
||||||
|
<span class="inline-flex items-center bg-brand-bg border border-brand-border rounded-full px-2.5 py-0.5 text-[11px] text-brand-navy/75" role="listitem">{{ org }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{# ===== PARTENAIRE DE CONFIANCE — CyberPerformance ===== #}
|
||||||
|
<section class="bg-white py-12" aria-labelledby="partner-title">
|
||||||
|
<div class="max-w-[1100px] mx-auto px-6">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<span id="partner-title" class="text-[10px] font-bold uppercase tracking-[0.20em] text-brand-navy/45">
|
||||||
|
Partenaire de confiance
|
||||||
|
</span>
|
||||||
|
<span class="flex-1 h-px max-w-[60px] bg-brand-border"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://cyberperformance.ca"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="group flex flex-wrap sm:flex-nowrap items-center gap-5 px-5 py-4 rounded border border-brand-border hover:border-brand-b1 transition-colors no-underline focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2"
|
||||||
|
aria-label="CyberPerformance — Marketing numérique, Lévis, QC. Site externe (s'ouvre dans un nouvel onglet)"
|
||||||
|
>
|
||||||
|
{# Icône SVG handshake générique (pas d'emoji, pas de logo bitmap) #}
|
||||||
|
<span class="shrink-0 w-12 h-12 grad-bg rounded flex items-center justify-center text-white" 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="M11 17l-2-2 2-2"/><path d="M13 7l2 2-2 2"/><path d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="hidden sm:block w-px h-8 bg-brand-border shrink-0"></span>
|
||||||
|
|
||||||
|
<span class="flex-1 min-w-0">
|
||||||
|
<span class="block text-sm font-bold text-brand-navy leading-tight">CyberPerformance</span>
|
||||||
|
<span class="block text-xs text-brand-navy/60 mt-0.5">Marketing numérique · Lévis, QC</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="shrink-0 inline-flex items-center gap-1.5 ml-auto text-xs text-brand-navy/55 group-hover:text-brand-b1 transition-colors">
|
||||||
|
<span>cyberperformance.ca</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="M7 17L17 7M7 7h10v10"/></svg>
|
||||||
|
<span class="sr-only">(s'ouvre dans un nouvel onglet)</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{# ===== TÉMOIGNAGES (placeholder pré-lancement) ===== #}
|
{# ===== TÉMOIGNAGES (placeholder pré-lancement) ===== #}
|
||||||
<section class="bg-brand-bg py-20" aria-labelledby="testimonials-title">
|
<section class="bg-brand-bg py-20" aria-labelledby="testimonials-title">
|
||||||
<div class="max-w-[1200px] mx-auto px-6">
|
<div class="max-w-[1200px] mx-auto px-6">
|
||||||
@@ -2019,15 +2237,19 @@
|
|||||||
<div class="relative max-w-[820px] mx-auto px-6 text-center">
|
<div class="relative max-w-[820px] mx-auto px-6 text-center">
|
||||||
<p class="eyebrow grad-text mb-4">PRÊT ?</p>
|
<p class="eyebrow grad-text mb-4">PRÊT ?</p>
|
||||||
<h2 id="cta-title" class="text-[clamp(2.25rem,4vw,3.5rem)] font-black mb-6">
|
<h2 id="cta-title" class="text-[clamp(2.25rem,4vw,3.5rem)] font-black mb-6">
|
||||||
Réservez votre <span class="grad-text">pré-inscription</span>.
|
Prêt à <span class="grad-text">protéger vos données</span> ?
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-lg text-white/80 mb-8">
|
<p class="text-lg text-white/80 mb-3">
|
||||||
|
Réservez une démonstration gratuite. Nous analyserons vos besoins et vous recommanderons le forfait adapté à votre réalité.
|
||||||
|
</p>
|
||||||
|
<p class="text-base text-white/65 mb-8">
|
||||||
Lancement printemps 2026. Les premiers utilisateurs bénéficient d'une remise de bienvenue et d'un accompagnement direct par notre équipe technique. Aucun engagement.
|
Lancement printemps 2026. Les premiers utilisateurs bénéficient d'une remise de bienvenue et d'un accompagnement direct par notre équipe technique. Aucun engagement.
|
||||||
</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='<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('Réserver ma démo gratuite', href='/contact', 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.5" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>') }}
|
||||||
|
{{ button('Pré-inscription par courriel', href='mailto:info@dictia.ca?subject=Pré-inscription%20DictIA', 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="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>
|
||||||
|
|
||||||
|
|||||||
25
tests/conftest.py
Normal file
25
tests/conftest.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
"""Test bootstrap — Windows shim for fcntl (used by src/init_db.py on POSIX).
|
||||||
|
|
||||||
|
Allows running tests on Windows even though the production app targets Linux.
|
||||||
|
Mirrors the stub used by serve_marketing.py for local preview.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
|
||||||
|
# Stub fcntl BEFORE pytest collects any test that imports src.app
|
||||||
|
if sys.platform.startswith('win') and 'fcntl' not in sys.modules:
|
||||||
|
fcntl_stub = types.ModuleType('fcntl')
|
||||||
|
fcntl_stub.LOCK_EX = 2
|
||||||
|
fcntl_stub.LOCK_NB = 4
|
||||||
|
fcntl_stub.LOCK_UN = 8
|
||||||
|
fcntl_stub.LOCK_SH = 1
|
||||||
|
fcntl_stub.flock = lambda *_a, **_kw: None
|
||||||
|
fcntl_stub.fcntl = lambda *_a, **_kw: 0
|
||||||
|
sys.modules['fcntl'] = fcntl_stub
|
||||||
|
|
||||||
|
# Minimal env so src/config/app_config.py doesn't sys.exit on missing config
|
||||||
|
os.environ.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite:///:memory:')
|
||||||
|
os.environ.setdefault('SECRET_KEY', 'test-secret-key')
|
||||||
|
os.environ.setdefault('TRANSCRIPTION_BASE_URL', 'http://local-stub')
|
||||||
|
os.environ.setdefault('TRANSCRIPTION_API_KEY', 'local-stub')
|
||||||
@@ -83,22 +83,30 @@ def test_landing_no_login_redirect_for_anonymous():
|
|||||||
|
|
||||||
|
|
||||||
def test_hero_has_h1_with_grad_text_accent():
|
def test_hero_has_h1_with_grad_text_accent():
|
||||||
"""Hero H1 contains grad-text span on the brand tagline."""
|
"""Hero H1 (round 3) contains the brand wordmark with grad-text accent on 'IA'.
|
||||||
|
|
||||||
|
Round 3 replaces the old tagline ('sans risquer votre permis') with the canonical
|
||||||
|
DictIA wordmark + the H2 phrase 'Transcription IA locale en 2 minutes'.
|
||||||
|
"""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
assert 'id="hero-title"' in body, "Missing hero-title id on H1"
|
assert 'id="hero-title"' in body, "Missing hero-title id on H1"
|
||||||
assert 'grad-text' in body, "Missing grad-text class somewhere"
|
assert 'grad-text' in body, "Missing grad-text class somewhere"
|
||||||
assert 'sans risquer votre permis' in body, "Missing key brand tagline"
|
# New canonical brand H2 phrase (cyan/grad on key claim)
|
||||||
|
assert 'Transcription IA locale en 2' in body, "Missing canonical H2 phrase"
|
||||||
|
# Hero word-staggered reveal hook on the wordmark
|
||||||
|
assert 'hero-h1-word' in body, "Missing word-staggered reveal class"
|
||||||
|
|
||||||
|
|
||||||
def test_hero_has_dual_cta():
|
def test_hero_has_dual_cta():
|
||||||
"""Hero has both primary (Réserver une démo) and ghost (Voir les tarifs) CTAs."""
|
"""Hero (round 3) has primary (Réserver une démo) and ghost (Voir les forfaits) CTAs."""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
assert 'href="/contact"' in body
|
assert 'href="/contact"' in body
|
||||||
assert 'href="/tarifs"' in body
|
assert 'href="/tarifs"' in body
|
||||||
assert 'Réserver une démo' in body or 'Réserver une démo' in body
|
assert 'Réserver une démo' in body or 'Réserver une démo' in body
|
||||||
assert 'Voir les tarifs' in body
|
# Round 3 canonical wording: 'Voir les forfaits' (matches dictia.ca/solutions/dictai)
|
||||||
|
assert 'Voir les forfaits' in body, "Round 3 secondary CTA must say 'Voir les forfaits'"
|
||||||
|
|
||||||
|
|
||||||
def test_hero_has_cosmic_orbs_background():
|
def test_hero_has_cosmic_orbs_background():
|
||||||
@@ -121,25 +129,28 @@ def test_hero_has_social_proof_microcopy():
|
|||||||
|
|
||||||
|
|
||||||
def test_hero_has_staggered_animations():
|
def test_hero_has_staggered_animations():
|
||||||
"""Hero elements use tc-fade-in-up with staggered delays."""
|
"""Hero (round 3) elements use tc-fade-in-up with staggered delays — canonical cadence.
|
||||||
|
|
||||||
|
Round 3 staggers : 0 (back-link), 75 (eyebrow), 200 (3-step flow),
|
||||||
|
280 (H2 phrase), 360 (sub), 440 (stats), 520 (CTAs), 600 (social proof).
|
||||||
|
"""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
assert 'animate-tc-fade-in-up' in body, "Missing fade-in animation"
|
assert 'animate-tc-fade-in-up' in body, "Missing fade-in animation"
|
||||||
assert 'animation-delay: 0ms' in body
|
for delay in ['0ms', '75ms', '200ms', '280ms', '360ms', '440ms', '520ms', '600ms']:
|
||||||
assert 'animation-delay: 75ms' in body
|
assert f'animation-delay: {delay}' in body, f"Missing staggered delay {delay}"
|
||||||
assert 'animation-delay: 150ms' in body
|
|
||||||
assert 'animation-delay: 300ms' in body
|
|
||||||
assert 'animation-delay: 400ms' in body
|
|
||||||
assert 'animation-fill-mode: backwards' in body, \
|
assert 'animation-fill-mode: backwards' in body, \
|
||||||
"Missing animation-fill-mode (causes flash before delay fires)"
|
"Missing animation-fill-mode (causes flash before delay fires)"
|
||||||
|
|
||||||
|
|
||||||
def test_hero_eyebrow_has_brand_messaging():
|
def test_hero_eyebrow_has_brand_messaging():
|
||||||
"""Hero eyebrow declares the 3 brand pillars."""
|
"""Hero eyebrow declares the 3 brand pillars (round 3 uses OQLF NBSP : LOI 25)."""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
assert 'TRANSCRIPTION IA' in body
|
assert 'TRANSCRIPTION IA' in body
|
||||||
assert 'CONFORME LOI 25' in body
|
# OQLF-conformant : non-breaking space before "25" (NBSP entity)
|
||||||
|
assert 'CONFORME LOI 25' in body or 'CONFORME LOI 25' in body, \
|
||||||
|
"Missing 'CONFORME LOI 25' eyebrow (with or without NBSP)"
|
||||||
assert 'QU' in body # Either QUÉBEC or QUÉBEC
|
assert 'QU' in body # Either QUÉBEC or QUÉBEC
|
||||||
|
|
||||||
|
|
||||||
@@ -665,31 +676,32 @@ def test_testimonials_use_personas_not_fake_names():
|
|||||||
assert name not in body, f"Forbidden fabricated testimonial name: {name}"
|
assert name not in body, f"Forbidden fabricated testimonial name: {name}"
|
||||||
|
|
||||||
|
|
||||||
def test_faq_section_with_7_questions():
|
def test_faq_section_with_10_questions():
|
||||||
"""FAQ section present with 7 questions."""
|
"""FAQ section (round 3) present with 10 canonical questions from dictai-page-content.tsx."""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
assert 'faq-title' in body
|
assert 'faq-title' in body
|
||||||
# 7 panel IDs (loop.index is 1-indexed)
|
# 10 panel IDs (loop.index is 1-indexed)
|
||||||
for i in range(1, 8):
|
for i in range(1, 11):
|
||||||
assert f'id="faq-panel-{i}"' in body, f"Missing FAQ panel {i}"
|
assert f'id="faq-panel-{i}"' in body, f"Missing FAQ panel {i}"
|
||||||
# Question topic anchors
|
# Round 3 canonical topic anchors (sourced from dictai-page-content.tsx)
|
||||||
topics = ['Loi', 'Cloud et DictIA on-premise', 'fran', 'audiences', 'formats',
|
topics = ['Comment fonctionne la transcription', 'formats audio', '1 heure d',
|
||||||
'résilie', 'AGPL']
|
'confidentielle', 'Teams Copilot', 'Otter.ai', 'Barreau du Qu',
|
||||||
|
'Clio Manage', 'connaissances techniques', 'open source']
|
||||||
for topic in topics:
|
for topic in topics:
|
||||||
assert topic in body, f"FAQ missing topic anchor: {topic}"
|
assert topic in body, f"FAQ missing topic anchor: {topic}"
|
||||||
|
|
||||||
|
|
||||||
def test_faq_alpine_accordion_bindings():
|
def test_faq_alpine_accordion_bindings():
|
||||||
"""FAQ uses Alpine.js x-data + @click + :aria-expanded for accessible accordion."""
|
"""FAQ uses Alpine.js x-data + @click + :aria-expanded for accessible accordion (10 items)."""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
# 7 x-data="{ open: false }" instances
|
# 10 x-data="{ open: false }" instances (round 3 enrichment)
|
||||||
assert body.count('x-data="{ open: false }"') == 7, \
|
assert body.count('x-data="{ open: false }"') == 10, \
|
||||||
"FAQ must have 7 independent Alpine accordion items"
|
"FAQ must have 10 independent Alpine accordion items"
|
||||||
# Each toggle button has @click and :aria-expanded
|
# Each toggle button has @click and :aria-expanded
|
||||||
assert body.count('@click="open = !open"') == 7
|
assert body.count('@click="open = !open"') == 10
|
||||||
assert body.count(':aria-expanded="open.toString()"') == 7
|
assert body.count(':aria-expanded="open.toString()"') == 10
|
||||||
# Use built-in x-transition (NOT x-collapse plugin which is not bundled)
|
# Use built-in x-transition (NOT x-collapse plugin which is not bundled)
|
||||||
assert 'x-collapse' not in body, "Must NOT use x-collapse plugin (not loaded — use x-transition)"
|
assert 'x-collapse' not in body, "Must NOT use x-collapse plugin (not loaded — use x-transition)"
|
||||||
assert 'x-transition.opacity' in body, "FAQ panels must use built-in x-transition"
|
assert 'x-transition.opacity' in body, "FAQ panels must use built-in x-transition"
|
||||||
@@ -720,7 +732,8 @@ def test_faq_jsonld_schema_present():
|
|||||||
assert parsed['@context'] == 'https://schema.org'
|
assert parsed['@context'] == 'https://schema.org'
|
||||||
assert parsed['@type'] == 'FAQPage'
|
assert parsed['@type'] == 'FAQPage'
|
||||||
assert isinstance(parsed['mainEntity'], list)
|
assert isinstance(parsed['mainEntity'], list)
|
||||||
assert len(parsed['mainEntity']) == 7, "FAQPage must contain exactly 7 questions"
|
# Round 3: enriched to 10 canonical questions from dictai-page-content.tsx
|
||||||
|
assert len(parsed['mainEntity']) == 10, "FAQPage must contain exactly 10 questions (round 3)"
|
||||||
for q in parsed['mainEntity']:
|
for q in parsed['mainEntity']:
|
||||||
assert q['@type'] == 'Question'
|
assert q['@type'] == 'Question'
|
||||||
assert q['acceptedAnswer']['@type'] == 'Answer'
|
assert q['acceptedAnswer']['@type'] == 'Answer'
|
||||||
@@ -729,18 +742,25 @@ def test_faq_jsonld_schema_present():
|
|||||||
|
|
||||||
|
|
||||||
def test_cta_final_section():
|
def test_cta_final_section():
|
||||||
"""CTA final section with mailto pré-inscription + ghost button to #tarifs."""
|
"""CTA final (round 3) — primary démo gratuite + mailto pré-inscription + ghost button to #tarifs."""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
assert 'cta-title' in body
|
assert 'cta-title' in body
|
||||||
assert 'pré-inscription' in body or 'pré-inscription' in body
|
# Round 3 wording reinforced: "Prêt à protéger vos données" + démo gratuite
|
||||||
|
assert 'prot' in body and 'donn' in body, "Missing 'protéger vos données' headline"
|
||||||
|
assert 'démo gratuite' in body or 'démo gratuite' in body, \
|
||||||
|
"Round 3 primary CTA must say 'démo gratuite'"
|
||||||
|
# Pré-inscription wording (any case) preserved as secondary path
|
||||||
|
assert 'pré-inscription' in body or 'pré-inscription' in body \
|
||||||
|
or 'Pré-inscription' in body or 'Pré-inscription' in body, \
|
||||||
|
"Pré-inscription wording must be preserved"
|
||||||
# mailto with subject
|
# mailto with subject
|
||||||
assert 'href="mailto:info@dictia.ca?subject=Pr%C3%A9-inscription%20DictIA"' in body or \
|
assert 'href="mailto:info@dictia.ca?subject=Pr%C3%A9-inscription%20DictIA"' in body or \
|
||||||
'href="mailto:info@dictia.ca?subject=Pré-inscription%20DictIA"' in body, \
|
'href="mailto:info@dictia.ca?subject=Pré-inscription%20DictIA"' in body, \
|
||||||
"CTA must have mailto with subject prefilled"
|
"CTA must have mailto with subject prefilled"
|
||||||
# Anchor link to existing #tarifs section
|
# Anchor link to existing #tarifs section
|
||||||
assert 'href="#tarifs"' in body, "Secondary CTA must anchor to pricing"
|
assert 'href="#tarifs"' in body, "Secondary CTA must anchor to pricing"
|
||||||
# Ghost variant button
|
# Ghost variant button still in use (mailto + #tarifs)
|
||||||
assert 'border-white/[0.08]' in body # ghost button class
|
assert 'border-white/[0.08]' in body # ghost button class
|
||||||
|
|
||||||
|
|
||||||
@@ -839,12 +859,16 @@ def test_round2_no_external_js_libs_added():
|
|||||||
|
|
||||||
|
|
||||||
def test_round2_preserves_existing_sections():
|
def test_round2_preserves_existing_sections():
|
||||||
"""Round 2 inserts must NOT remove Hero / Pipeline / Hub / Bento / Comparatif / Conformité."""
|
"""Round 2 + 3 inserts must NOT remove Hero / Pipeline / Hub / Bento / Comparatif / Conformité.
|
||||||
|
|
||||||
|
NOTE: round 3 replaced the hero copy ('sans risquer votre permis' → canonical wordmark
|
||||||
|
+ 'Transcription IA locale en 2 minutes'). The hero ID + pipeline are still required.
|
||||||
|
"""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
# Hero (round 0)
|
# Hero (round 3 canonical hero replaces round 0)
|
||||||
assert 'hero-title' in body
|
assert 'hero-title' in body
|
||||||
assert 'sans risquer votre permis' in body
|
assert 'Transcription IA locale en 2' in body, "Round 3 hero canonical phrase missing"
|
||||||
# Pipeline (round 1) — auto-advance + 4 nodes
|
# Pipeline (round 1) — auto-advance + 4 nodes
|
||||||
assert 'pipeline-title' in body
|
assert 'pipeline-title' in body
|
||||||
assert 'Du fichier au résumé' in body
|
assert 'Du fichier au résumé' in body
|
||||||
@@ -880,7 +904,8 @@ def test_routes_passes_testimonials_and_faq_to_template():
|
|||||||
assert hasattr(routes, 'TESTIMONIALS'), "Module must define TESTIMONIALS list"
|
assert hasattr(routes, 'TESTIMONIALS'), "Module must define TESTIMONIALS list"
|
||||||
assert hasattr(routes, 'FAQ'), "Module must define FAQ list"
|
assert hasattr(routes, 'FAQ'), "Module must define FAQ list"
|
||||||
assert len(routes.TESTIMONIALS) == 3, "Must have 3 placeholder testimonials"
|
assert len(routes.TESTIMONIALS) == 3, "Must have 3 placeholder testimonials"
|
||||||
assert len(routes.FAQ) == 7, "Must have 7 FAQ entries"
|
# Round 3: enriched FAQ from 7 to 10 canonical questions (sourced from dictai-page-content.tsx)
|
||||||
|
assert len(routes.FAQ) == 10, "Must have 10 FAQ entries (round 3)"
|
||||||
# Each testimonial must NOT contain a 'quote' field (no fabricated quotes pre-launch)
|
# Each testimonial must NOT contain a 'quote' field (no fabricated quotes pre-launch)
|
||||||
for t in routes.TESTIMONIALS:
|
for t in routes.TESTIMONIALS:
|
||||||
assert 'quote' not in t, "Pre-launch testimonials must not contain quote fields"
|
assert 'quote' not in t, "Pre-launch testimonials must not contain quote fields"
|
||||||
|
|||||||
Reference in New Issue
Block a user