L'abstraction en 4 catégories (commit 8a7650f) cachait les 13 modes
derrière des regroupements ; l'utilisateur ne VOYAIT plus les 6 boutons
features originaux et percevait que des fonctions avaient disparu.
Refonte v2 :
- Bottom tab bar : 6 boutons FEATURES originaux 1-6 (Trans/Diari/Lang/Exp/Users/Part) — visibles, cliquables
- Right panel : grid 3×4 = 12 boutons FEATURES 1-12 (IA mode 0 reste accessible via Brain card)
- Mobile pills : 12 features scrollables horizontalement
- Auto-cycle : 1→12 skip 0, 1100ms each (~13s cycle complet)
- Manual click : isManual 4500ms reset puis reprend auto
- SUPPRESSION : CATEGORIES array + activeCategory state + handleCategorySelect + sub-mode dots indicator
- AJOUT : featureGridLabel() pour labels compacts (Recording/Recherche IA/Résumés/Intégrations/Audit trail/Conformité)
Préservé : phone shell statique, palette brand stricte b1/b2/b3, IA Mistral
card inchangée, 13 templates de modes (0-12) intacts, eyebrow brand-navy noir,
WCAG aria-labels + aria-pressed, prefers-reduced-motion guard.
Test adapté : assert featureGridLabel + 12 fonctions + labels grid.
2251 lines
145 KiB
HTML
2251 lines
145 KiB
HTML
{% extends 'marketing/base.html' %}
|
||
|
||
{% block title %}Fonctionnalités DictIA — WhisperX FR-CA, diarisation 8 locuteurs, Mistral 7B local, Q&R, exports DOCX/PDF/SRT{% endblock %}
|
||
{% block description %}Toutes les fonctionnalités de DictIA : transcription WhisperX Large-v3 fine-tunée FR-CA, diarisation pyannote 8 locuteurs, résumés Mistral 7B local, chat Q&R RAG, synchronisation audio-texte, exports multi-formats, intégrations Word, Outlook, Teams, Notion, Obsidian, Zapier.{% endblock %}
|
||
|
||
{% block head_extra %}
|
||
{# Page-scoped CSS — animations subtiles sans dépendre d'un rebuild Tailwind ; respecte prefers-reduced-motion via base.css. #}
|
||
<style>
|
||
/* Animated underline H2 — visible classe ajoutée via IntersectionObserver */
|
||
.ani-underline { position: relative; display: inline-block; }
|
||
.ani-underline::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 50%;
|
||
bottom: -10px;
|
||
width: 0;
|
||
height: 3px;
|
||
background: linear-gradient(118deg, #2563eb, #06b6d4 52%, #06b6d4);
|
||
transform: translateX(-50%);
|
||
transition: width 600ms ease-out;
|
||
}
|
||
.ani-underline.is-visible::after { width: 56px; }
|
||
|
||
/* Stagger fade-in cards — class ajoutée par IntersectionObserver */
|
||
.ani-fade { opacity: 0; transform: translateY(16px); transition: opacity 600ms ease-out, transform 600ms ease-out; transition-delay: var(--delay, 0ms); }
|
||
.ani-fade.is-visible { opacity: 1; transform: translateY(0); }
|
||
|
||
/* Hover lift cards */
|
||
.ani-lift { transition: transform 200ms ease-out, box-shadow 200ms ease-out; }
|
||
.ani-lift:hover { transform: translateY(-2px); box-shadow: 0 8px 32px rgba(37,99,235, 0.18); }
|
||
|
||
/* Sticky sub-nav active link */
|
||
.subnav-link[aria-current="true"] { color: #2563eb; }
|
||
.subnav-link[aria-current="true"]::after {
|
||
content: '';
|
||
display: block;
|
||
height: 2px;
|
||
margin-top: 4px;
|
||
background: linear-gradient(118deg, #2563eb, #06b6d4 52%, #06b6d4);
|
||
}
|
||
|
||
/* Cosmic float orbs (n'utilise pas tc-float-y pour éviter rebuild) */
|
||
@keyframes orb-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-12px); } }
|
||
.orb-float-a { animation: orb-float 8s ease-in-out infinite; }
|
||
.orb-float-b { animation: orb-float 11s ease-in-out infinite reverse; }
|
||
|
||
/* Cloud PRO pulse glow (recommended tier) */
|
||
@keyframes card-pulse-glow {
|
||
0%, 100% { box-shadow: 0 4px 20px rgba(37,99,235, 0.28); }
|
||
50% { box-shadow: 0 12px 40px rgba(37,99,235, 0.5); }
|
||
}
|
||
.card-pulse-glow { animation: card-pulse-glow 3s ease-in-out infinite; }
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.ani-fade { opacity: 1; transform: none; transition: none; }
|
||
.ani-underline::after { transition: none; width: 56px; }
|
||
.ani-lift { transition: none; }
|
||
.ani-lift:hover { transform: none; }
|
||
.orb-float-a, .orb-float-b, .card-pulse-glow { animation: none; }
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
{%- set icon_microphone = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-7 h-7" aria-hidden="true"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="22"/></svg>' -%}
|
||
{%- set icon_users = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-7 h-7" aria-hidden="true"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>' -%}
|
||
{%- set icon_document = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-7 h-7" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/></svg>' -%}
|
||
{%- set icon_chat = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-7 h-7" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>' -%}
|
||
{%- set icon_sync = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-7 h-7" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10"/><path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14"/></svg>' -%}
|
||
{%- set icon_export = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-7 h-7" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>' -%}
|
||
{%- set icon_check = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4 mt-0.5 flex-shrink-0 text-brand-b3" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>' -%}
|
||
{%- set icon_link = '<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="M5 12h14M13 5l7 7-7 7"/></svg>' -%}
|
||
|
||
{# ===== HERO ===== #}
|
||
<section class="relative overflow-hidden bg-brand-navy text-white py-24 md:py-28" aria-labelledby="page-title">
|
||
{# Cosmic orbs background — float animation subtile #}
|
||
<div class="absolute inset-0 pointer-events-none" aria-hidden="true">
|
||
<div class="orb-float-a absolute top-1/4 left-1/4 w-[600px] h-[600px] rounded-full"
|
||
style="background: radial-gradient(circle, rgba(37,99,235,0.16) 0%, transparent 60%); filter: blur(40px);"></div>
|
||
<div class="orb-float-b absolute top-1/2 right-1/4 w-[500px] h-[500px] rounded-full"
|
||
style="background: radial-gradient(circle, rgba(6,182,212,0.07) 0%, transparent 60%); filter: blur(40px);"></div>
|
||
<div class="absolute inset-0"
|
||
style="background-image: linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px); background-size: 40px 40px;"></div>
|
||
<div class="absolute top-1/3 left-0 right-0 h-px"
|
||
style="background: linear-gradient(90deg, transparent, rgba(37,99,235,0.3), rgba(6,182,212,0.2), transparent);"></div>
|
||
</div>
|
||
|
||
<div class="relative max-w-[1200px] mx-auto px-6 text-center">
|
||
<p class="eyebrow grad-text mb-5 animate-tc-fade-in-up" style="animation-delay: 0ms; animation-fill-mode: backwards;">
|
||
FONCTIONNALITÉS · WHISPERX · MISTRAL 7B
|
||
</p>
|
||
<h1 id="page-title" class="text-[clamp(2.25rem,4vw,3.75rem)] font-black leading-[1.05] mb-5 max-w-3xl mx-auto animate-tc-fade-in-up" style="animation-delay: 75ms; animation-fill-mode: backwards;">
|
||
Tout ce qu'il vous faut pour transcrire <span class="grad-text">en restant chez soi</span>.
|
||
</h1>
|
||
<p class="text-lg text-white/70 max-w-2xl mx-auto mb-12 animate-tc-fade-in-up" style="animation-delay: 150ms; animation-fill-mode: backwards;">
|
||
Pile technique 100 % québécoise. Inférence sur GPU local ou OVH Beauharnois. Aucun transit cloud américain. Conçu avec 9 ordres professionnels.
|
||
</p>
|
||
|
||
{# Stats hero — counter via Alpine x-data + IntersectionObserver pour déclenchement #}
|
||
<div class="grid grid-cols-2 md:grid-cols-4 gap-8 max-w-4xl mx-auto" role="list">
|
||
{% for stat in [
|
||
{'target': 95, 'suffix': ' %+', 'label': "précision FR-CA", 'sub': 'WhisperX Large-v3'},
|
||
{'target': 30, 'suffix': '×', 'label': "vitesse temps réel", 'sub': '1 h audio → ~5 min'},
|
||
{'target': 8, 'suffix': '', 'label': "locuteurs simultanés", 'sub': 'pyannote-audio'},
|
||
{'target': 0, 'suffix': '', 'label': "transit cloud", 'sub': '100 % local'}
|
||
] %}
|
||
<div class="text-center" role="listitem"
|
||
x-data="{ value: 0, target: {{ stat.target }} }"
|
||
x-init="
|
||
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||
if (reduced) { value = target; return; }
|
||
const io = new IntersectionObserver((entries) => {
|
||
if (!entries[0].isIntersecting) return;
|
||
const start = performance.now();
|
||
const dur = 1200;
|
||
const tick = (now) => {
|
||
const t = Math.min(1, (now - start) / dur);
|
||
value = Math.round(target * (1 - Math.pow(1 - t, 3)));
|
||
if (t < 1) requestAnimationFrame(tick);
|
||
};
|
||
requestAnimationFrame(tick);
|
||
io.disconnect();
|
||
}, { threshold: 0.5 });
|
||
io.observe($el);
|
||
">
|
||
<div class="text-5xl font-black grad-text mb-2 tabular-nums">
|
||
<span x-text="value"></span><span>{{ stat.suffix | safe }}</span>
|
||
</div>
|
||
<div class="text-sm font-semibold text-white mb-1">{{ stat.label }}</div>
|
||
<div class="text-xs text-white/60">{{ stat.sub | safe }}</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{# ===== STICKY SUB-NAV ===== #}
|
||
<nav class="sticky top-[62px] z-40 bg-white/95 backdrop-blur-sm border-b border-brand-border" aria-label="Navigation des sections de la page"
|
||
x-data="{ active: 'fonctionnalites' }"
|
||
x-init="
|
||
const sections = ['fonctionnalites', 'integrations', 'architecture', 'conformite-resume'];
|
||
const io = new IntersectionObserver((entries) => {
|
||
entries.forEach(e => {
|
||
if (e.isIntersecting && e.intersectionRatio > 0.15) active = e.target.id;
|
||
});
|
||
}, { rootMargin: '-120px 0px -55% 0px', threshold: [0, 0.15, 0.3] });
|
||
sections.forEach(id => { const el = document.getElementById(id); if (el) io.observe(el); });
|
||
">
|
||
<div class="max-w-[1200px] mx-auto px-6">
|
||
<ul class="flex flex-wrap gap-x-6 gap-y-1 py-3 text-sm font-semibold text-brand-navy/70" role="list">
|
||
<li><a href="#fonctionnalites" class="subnav-link inline-block py-1 hover:text-brand-b1 transition-colors focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2"
|
||
:aria-current="active === 'fonctionnalites' ? 'true' : 'false'">Fonctionnalités</a></li>
|
||
<li><a href="#integrations" class="subnav-link inline-block py-1 hover:text-brand-b1 transition-colors focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2"
|
||
:aria-current="active === 'integrations' ? 'true' : 'false'">Intégrations</a></li>
|
||
<li><a href="#architecture" class="subnav-link inline-block py-1 hover:text-brand-b1 transition-colors focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2"
|
||
:aria-current="active === 'architecture' ? 'true' : 'false'">Architecture</a></li>
|
||
<li><a href="#conformite-resume" class="subnav-link inline-block py-1 hover:text-brand-b1 transition-colors focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2"
|
||
:aria-current="active === 'conformite-resume' ? 'true' : 'false'">Conformité</a></li>
|
||
</ul>
|
||
</div>
|
||
</nav>
|
||
|
||
{# ===== 6 FONCTIONNALITÉS PRINCIPALES =====
|
||
NOTE: bento card content is duplicated between landing.html and fonctionnalites.html.
|
||
When editing, sync both files. Future refactor: extract to _partials/_bento_features.html.
|
||
#}
|
||
<section id="fonctionnalites" class="bg-white py-20 scroll-mt-32" aria-labelledby="features-title">
|
||
<div class="max-w-[1200px] mx-auto px-6">
|
||
<div class="text-center max-w-3xl mx-auto mb-14">
|
||
<p class="eyebrow grad-text mb-4">SIX FONCTIONNALITÉS</p>
|
||
<h2 id="features-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black text-brand-navy">
|
||
<span class="ani-underline" data-ani-underline>Conçu pour les pros qui parlent et écrivent toute la journée.</span>
|
||
</h2>
|
||
<p class="text-lg text-brand-navy/70 mt-6">
|
||
Chaque module fonctionne hors-ligne sur votre matériel ou notre VPS Québec OVH. Aucun appel à OpenAI, Microsoft Copilot ou Google.
|
||
</p>
|
||
</div>
|
||
|
||
{% from 'macros/bento.html' import bento_card %}
|
||
{%- set features = [
|
||
{
|
||
'n': '01',
|
||
'title': 'Transcription IA locale FR-CA',
|
||
'desc': 'WhisperX Large-v3 entraîné sur des millions d\'heures de québécois juridique, médical et gouvernemental. 95 %+ dès la première utilisation, sans aucun ajustement manuel.',
|
||
'icon': icon_microphone,
|
||
'chips': ['WhisperX Large-v3', 'GPU RTX 8 ou 16 Go', '1 h audio → ~5 min', '< 5 % taux d\'erreur', '100 % local · hors-ligne']
|
||
},
|
||
{
|
||
'n': '02',
|
||
'title': 'Identification des locuteurs (diarisation)',
|
||
'desc': 'Jusqu\'à 8 interlocuteurs distingués automatiquement. Chaque prise de parole est étiquetée et horodatée pour une lecture nette des comités, audiences et entrevues.',
|
||
'icon': icon_users,
|
||
'chips': ['pyannote-audio 3.x', '8 personnes max', 'Étiquettes auto', 'Horodatage word-level']
|
||
},
|
||
{
|
||
'n': '03',
|
||
'title': 'Résumés & points d\'action',
|
||
'desc': 'Résumé exécutif, décisions clés et liste d\'actions générés en français. Format avocat, notaire, CPA ou médecin selon votre profil. Récupérez ~2 h/jour de rédaction.',
|
||
'icon': icon_document,
|
||
'chips': ['Mistral Nemo 12B (Cloud) · Mistral 7B (Local)', 'Templates pro', 'Décisions + actions', '2 h/jour récupérées']
|
||
},
|
||
{
|
||
'n': '04',
|
||
'title': 'Chat IA sur enregistrement (Q&R)',
|
||
'desc': 'Posez vos questions directement sur l\'audio : « Quel montant a été mentionné ? » ou « Quels engagements ont été pris ? » — réponses instantanées en langage naturel.',
|
||
'icon': icon_chat,
|
||
'chips': ['Mistral 7B local', 'RAG local', 'sentence-transformers', 'Recherche contextuelle']
|
||
},
|
||
{
|
||
'n': '05',
|
||
'title': 'Synchronisation audio-texte',
|
||
'desc': 'Cliquez sur n\'importe quel mot pour sauter à ce moment dans l\'audio. Suivi automatique en lecture avec mot actif surligné — révision et référencement instantanés.',
|
||
'icon': icon_sync,
|
||
'chips': ['Timestamps word-level', 'Lecture guidée', 'Mot actif surligné', 'Navigation par mot clé']
|
||
},
|
||
{
|
||
'n': '06',
|
||
'title': 'Recherche sémantique & exports',
|
||
'desc': 'Recherchez par sens et non seulement par mot-clé. Exportez vers DOCX, PDF, SRT, VTT, TXT, JSON, MD — compatible Obsidian, Notion, Logseq via Markdown.',
|
||
'icon': icon_export,
|
||
'chips': ['DOCX, PDF, SRT, VTT', 'TXT, JSON, MD', 'Obsidian · Notion · Logseq', 'Format avocat / notaire / CPA']
|
||
}
|
||
] -%}
|
||
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-[1.5px] bg-brand-border rounded overflow-hidden">
|
||
{% for f in features %}
|
||
<article class="ani-fade ani-lift relative bg-brand-navy2 p-6 overflow-hidden border border-white/[0.045]"
|
||
style="--delay: {{ loop.index0 * 60 }}ms;"
|
||
data-ani-fade>
|
||
<div class="absolute top-2 right-4 text-[80px] font-black grad-text opacity-20 leading-none" aria-hidden="true">{{ f.n }}</div>
|
||
<div class="relative">
|
||
<div class="text-brand-b1 mb-4" aria-hidden="true">{{ f.icon | safe }}</div>
|
||
<h3 class="text-lg font-bold mb-2 text-white">{{ f.title | safe }}</h3>
|
||
<p class="text-sm text-white/70 leading-relaxed mb-4">{{ f.desc | safe }}</p>
|
||
<ul class="flex flex-wrap gap-1.5" role="list">
|
||
{% for chip in f.chips %}
|
||
<li class="text-[11px] font-mono font-medium text-white/80 bg-white/[0.06] border border-white/[0.08] px-2 py-1 rounded-full">{{ chip | safe }}</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</div>
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
{# Microcopy LPC art. 219 — méthodologie 95% #}
|
||
<p class="text-xs text-brand-navy/70 text-center mt-8 max-w-2xl mx-auto">
|
||
Précision mesurée sur un échantillon interne d'audio professionnel québécois (juridique, médical, municipal) — méthodologie disponible sur demande à <a href="mailto:info@dictia.ca" class="hover:text-brand-navy underline">info@dictia.ca</a>.
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
{# ===== COMMENT ÇA MARCHE — DASHBOARD HOLOGRAPHIQUE (4 CATÉGORIES × 12 SOUS-MODES) =====
|
||
Reproduction fidèle de DashboardHolographique (Website-Sanity/dictai-narrative.tsx),
|
||
refonte stratégique 2026-04-29 : 4 catégories (Capture · IA · Distribution · Gouvernance)
|
||
regroupant 12 sous-modes (6 historiques + 6 nouveaux : Recording, Recherche, Résumé,
|
||
Intégrations, Audit, Loi 25, AGPL).
|
||
Phone container central + 12 sous-modes + IA Mistral 7B premium card + grid 4 catégories.
|
||
Auto-cycle : sous-mode toutes les 1100ms ; passage à catégorie suivante quand fin atteinte.
|
||
Click manuel → 4500ms isManual. Catégorie 4 (Gouvernance) inclut audit trail / Loi 25 / AGPL.
|
||
#}
|
||
<section class="bg-brand-bg py-20 relative overflow-hidden" aria-labelledby="how-it-works-title">
|
||
<style>
|
||
/* ============ DECORATIVE BACKGROUND ============ */
|
||
.dictia-section-grid {
|
||
background-image:
|
||
linear-gradient(to right, rgba(15,23,42,0.05) 1px, transparent 1px),
|
||
linear-gradient(to bottom, rgba(15,23,42,0.05) 1px, transparent 1px);
|
||
background-size: 56px 56px;
|
||
mask-image: radial-gradient(ellipse 80% 60% at 50% 50%, black 0%, transparent 75%);
|
||
-webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 50%, black 0%, transparent 75%);
|
||
}
|
||
.dictia-orb-float-1 { animation: dictia-orb-1 14s ease-in-out infinite; will-change: transform; }
|
||
.dictia-orb-float-2 { animation: dictia-orb-2 18s ease-in-out infinite; will-change: transform; }
|
||
@keyframes dictia-orb-1 { 0%, 100% { transform: translate(0, 0); } 50% { transform: translate(30px, -20px); } }
|
||
@keyframes dictia-orb-2 { 0%, 100% { transform: translate(0, 0); } 50% { transform: translate(-25px, 25px); } }
|
||
|
||
/* ============ PHONE BEZEL & DEVICE FRAME ============ */
|
||
.dictia-phone-shell {
|
||
border-radius: 44px;
|
||
position: relative;
|
||
will-change: box-shadow, border-color;
|
||
}
|
||
/* Inner screen border : effet "écran encastré" subtle entre bezel et écran */
|
||
.dictia-phone-shell::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 2px;
|
||
border-radius: 42px;
|
||
border: 1px solid rgba(255,255,255,0.05);
|
||
pointer-events: none;
|
||
background: linear-gradient(180deg, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0) 28%);
|
||
z-index: 0;
|
||
}
|
||
/* Inner screen seam : ligne 1px entre bezel + écran (look device authentique) */
|
||
.dictia-phone-screen-seam {
|
||
position: absolute;
|
||
inset: 6px;
|
||
border-radius: 38px;
|
||
pointer-events: none;
|
||
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 1px 0 0 rgba(255,255,255,0.06);
|
||
z-index: 1;
|
||
}
|
||
.dictia-phone-glow-ring { animation: dictia-glow-ring 3.6s ease-in-out infinite; will-change: opacity; }
|
||
@keyframes dictia-glow-ring { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.7; } }
|
||
|
||
/* ============ STATUS BAR + NOTCH + SPEAKER ============ */
|
||
.dictia-statusbar { font-variant-numeric: tabular-nums; }
|
||
|
||
/* ============ MIC SOUND WAVES (3 ripples) ============ */
|
||
.dictia-sound-ring {
|
||
position: absolute;
|
||
top: 50%; left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
border-radius: 50%;
|
||
border: 1.5px solid currentColor;
|
||
opacity: 0;
|
||
will-change: transform, opacity;
|
||
animation: dictia-sound-ring-anim 2.4s ease-out infinite;
|
||
}
|
||
.dictia-sound-ring.r1 { animation-delay: 0s; }
|
||
.dictia-sound-ring.r2 { animation-delay: 0.8s; }
|
||
.dictia-sound-ring.r3 { animation-delay: 1.6s; }
|
||
@keyframes dictia-sound-ring-anim {
|
||
0% { transform: translate(-50%, -50%) scale(0.4); opacity: 0.55; }
|
||
100% { transform: translate(-50%, -50%) scale(2.4); opacity: 0; }
|
||
}
|
||
|
||
/* Mic pulsing en haut du phone */
|
||
.dictia-mic-pulse { animation: dmic-pulse 1.2s ease-in-out infinite; display: inline-block; will-change: transform; }
|
||
@keyframes dmic-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.18); } }
|
||
|
||
/* ============ IA CARD ============ */
|
||
.ia-ambient-glow { animation: ia-glow 3s ease-in-out infinite; }
|
||
@keyframes ia-glow { 0%, 100% { opacity: 0.4; } 50% { opacity: 0.75; } }
|
||
.ia-brain-glow { animation: ia-brain 2.2s ease-in-out infinite; }
|
||
@keyframes ia-brain { 0%, 100% { opacity: 0.2; } 50% { opacity: 0.55; } }
|
||
.ia-local-badge { animation: ia-local 2.5s ease-in-out infinite; }
|
||
@keyframes ia-local { 0%, 100% { opacity: 0.75; } 50% { opacity: 1; } }
|
||
|
||
/* Curseur clignotant (transcription, IA chat) */
|
||
.dictia-blink { animation: dictia-cursor-blink 0.6s steps(2, end) infinite; }
|
||
@keyframes dictia-cursor-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
|
||
|
||
/* Dot pulse */
|
||
.dictia-dot-pulse { animation: dictia-dot-pulse-anim 1.8s ease-in-out infinite; }
|
||
@keyframes dictia-dot-pulse-anim { 0%, 100% { opacity: 0.4; transform: scale(1); } 50% { opacity: 1; transform: scale(1.35); } }
|
||
|
||
/* REC dot (transcription) */
|
||
.dictia-rec-dot { animation: dictia-rec-pulse 1.4s ease-in-out infinite; }
|
||
@keyframes dictia-rec-pulse { 0%, 100% { opacity: 0.5; box-shadow: 0 0 0 0 rgba(239,68,68,0.6); } 50% { opacity: 1; box-shadow: 0 0 0 4px rgba(239,68,68,0); } }
|
||
|
||
/* Waveform bars */
|
||
.dictia-wave-bar { animation: dictia-wave 0.9s ease-in-out infinite; will-change: transform; transform-origin: bottom; }
|
||
@keyframes dictia-wave { 0%, 100% { transform: scaleY(0.3); } 50% { transform: scaleY(1); } }
|
||
|
||
/* Slide-up + fade-in (diarisation messages) */
|
||
.dictia-msg-in { animation: dictia-fade-up 0.3s ease-out both; }
|
||
@keyframes dictia-fade-up { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
|
||
|
||
/* Typing indicator (3 dots) */
|
||
.dictia-typing-dot { animation: dictia-typing 1.2s ease-in-out infinite; }
|
||
@keyframes dictia-typing { 0%, 60%, 100% { opacity: 0.25; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-2px); } }
|
||
|
||
/* Spring pop (langues, exports, users, share) */
|
||
.dictia-spring { animation: dictia-spring-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1) both; }
|
||
@keyframes dictia-spring-pop { 0% { opacity: 0; transform: scale(0.5); } 80% { transform: scale(1.08); } 100% { opacity: 1; transform: scale(1); } }
|
||
.dictia-spring-y { animation: dictia-spring-y 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) both; }
|
||
@keyframes dictia-spring-y { 0% { opacity: 0; transform: translateY(14px) scale(0.65); } 80% { transform: translateY(-2px) scale(1.05); } 100% { opacity: 1; transform: translateY(0) scale(1); } }
|
||
.dictia-fade-x { animation: dictia-fade-x 0.32s ease-out both; }
|
||
@keyframes dictia-fade-x { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: translateX(0); } }
|
||
.dictia-fade-y { animation: dictia-fade-y 0.3s ease-out both; }
|
||
@keyframes dictia-fade-y { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
||
|
||
/* Mode wrapper crossfade + scale */
|
||
.dictia-mode-fade {
|
||
animation: dictia-mode-crossfade 0.32s cubic-bezier(0.4, 0, 0.2, 1) both;
|
||
will-change: opacity, transform;
|
||
}
|
||
@keyframes dictia-mode-crossfade {
|
||
from { opacity: 0; transform: scale(0.96); }
|
||
to { opacity: 1; transform: scale(1); }
|
||
}
|
||
|
||
/* Lang highlight pulse (random langs glow) */
|
||
.dictia-lang-highlight { animation: dictia-lang-glow 2.4s ease-in-out infinite; }
|
||
@keyframes dictia-lang-glow {
|
||
0%, 100% { box-shadow: 0 0 0 0 currentColor; filter: brightness(1); }
|
||
50% { box-shadow: 0 0 8px 1px currentColor; filter: brightness(1.4); }
|
||
}
|
||
|
||
/* Connecting line (phone -> IA panel) */
|
||
.dictia-connecting-line { animation: dictia-flow 3s linear infinite; }
|
||
@keyframes dictia-flow { from { stroke-dashoffset: 24; } to { stroke-dashoffset: 0; } }
|
||
|
||
/* Auto pill */
|
||
.dictia-auto-pulse { animation: dictia-auto-pulse-anim 1.6s ease-in-out infinite; }
|
||
@keyframes dictia-auto-pulse-anim { 0%, 100% { transform: scale(1); opacity: 0.7; } 50% { transform: scale(1.4); opacity: 1; } }
|
||
|
||
/* Manuel countdown bar */
|
||
.dictia-countdown-fill { animation: dictia-countdown 4.5s linear forwards; }
|
||
@keyframes dictia-countdown { from { width: 100%; } to { width: 0%; } }
|
||
|
||
/* Feature info card hover */
|
||
.dictia-feature-card { transition: transform 0.25s ease, box-shadow 0.25s ease; }
|
||
.dictia-feature-card:hover { transform: translateY(-1px); }
|
||
|
||
/* Bottom feature button (tab indicator) */
|
||
.dictia-feat-btn { transition: transform 0.18s ease, background-color 0.2s ease, opacity 0.2s ease; will-change: transform; }
|
||
.dictia-feat-btn:hover { transform: scale(1.08); opacity: 1 !important; }
|
||
|
||
/* Hide scrollbar mobile pills */
|
||
.dictia-hide-scrollbar::-webkit-scrollbar { display: none; }
|
||
|
||
/* ============ INTERNAL SCROLL (modes content) — scrollbar invisible ============ */
|
||
.dictia-mode-scroll {
|
||
overflow-y: auto;
|
||
scrollbar-width: none;
|
||
-ms-overflow-style: none;
|
||
}
|
||
.dictia-mode-scroll::-webkit-scrollbar { display: none; }
|
||
|
||
/* Fade gradient bottom (suggest scroll continuation) */
|
||
.dictia-fade-bottom {
|
||
mask-image: linear-gradient(to bottom, black 0%, black 85%, transparent 100%);
|
||
-webkit-mask-image: linear-gradient(to bottom, black 0%, black 85%, transparent 100%);
|
||
}
|
||
|
||
/* ============ MOBILE OPTIMIZATIONS ============ */
|
||
@media (max-width: 767px) {
|
||
.dictia-statusbar,
|
||
.dictia-speaker-grille,
|
||
.dictia-camera-dot,
|
||
.dictia-notch,
|
||
.dictia-sound-ring,
|
||
.dictia-connecting-line-wrap,
|
||
.dictia-bg-orbs,
|
||
.dictia-bg-grid,
|
||
.dictia-wave-bar { display: none !important; }
|
||
|
||
/* Phone reste à largeur fixe — pas de stretch, scale léger si écran très étroit */
|
||
.dictia-phone-wrap { width: 280px !important; max-width: 100% !important; }
|
||
}
|
||
@media (max-width: 320px) {
|
||
.dictia-phone-wrap { transform: scale(0.92); transform-origin: top center; }
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.dictia-mic-pulse, .ia-ambient-glow, .ia-brain-glow, .ia-local-badge,
|
||
.dictia-blink, .dictia-dot-pulse, .dictia-msg-in, .dictia-spring,
|
||
.dictia-spring-y, .dictia-fade-x, .dictia-fade-y, .dictia-mode-fade,
|
||
.dictia-sound-ring, .dictia-orb-float-1, .dictia-orb-float-2,
|
||
.dictia-phone-glow-ring, .dictia-rec-dot, .dictia-wave-bar,
|
||
.dictia-typing-dot, .dictia-lang-highlight, .dictia-connecting-line,
|
||
.dictia-auto-pulse, .dictia-countdown-fill { animation: none !important; }
|
||
}
|
||
</style>
|
||
|
||
{# Decorative background : grid + floating orbs (desktop only) — brand-b1/b3 only #}
|
||
<div class="dictia-bg-grid absolute inset-0 dictia-section-grid pointer-events-none" aria-hidden="true"></div>
|
||
<div class="dictia-bg-orbs absolute inset-0 pointer-events-none overflow-hidden" aria-hidden="true">
|
||
<div class="dictia-orb-float-1 absolute" style="top:12%;left:6%;width:240px;height:240px;background:radial-gradient(circle, rgba(37,99,235,0.10) 0%, transparent 70%);filter:blur(80px);"></div>
|
||
<div class="dictia-orb-float-2 absolute" style="bottom:8%;right:8%;width:280px;height:280px;background:radial-gradient(circle, rgba(192,38,211,0.10) 0%, transparent 70%);filter:blur(90px);"></div>
|
||
</div>
|
||
|
||
<div class="max-w-[1200px] mx-auto px-6 relative">
|
||
<div class="text-center max-w-2xl mx-auto mb-10">
|
||
{# Eyebrow : typo NOIRE sur fond clair, dot pulse brand-b3 pour accent #}
|
||
<p class="eyebrow text-brand-navy mb-4 inline-flex items-center gap-2 justify-center px-3 py-1 rounded-full font-mono uppercase tracking-widest text-xs font-bold"
|
||
style="background:linear-gradient(135deg, rgba(6,182,212,0.10), rgba(192,38,211,0.10)); border:1px solid rgba(192,38,211,0.20);">
|
||
<span class="dictia-dot-pulse inline-block w-1.5 h-1.5 rounded-full bg-brand-b3" aria-hidden="true"></span>
|
||
COMMENT ÇA MARCHE
|
||
</p>
|
||
<h2 id="how-it-works-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black text-brand-navy mb-4 leading-tight">
|
||
Du fichier au résumé — <span class="grad-text">en temps réel</span>
|
||
</h2>
|
||
<p class="text-base text-brand-navy/70 mb-5">
|
||
Survolez une fonctionnalité pour voir la machine en action. Glissez pour calculer votre gain de productivité.
|
||
<a href="/contact" class="grad-text font-semibold hover:underline whitespace-nowrap ml-1">Voir une démo →</a>
|
||
</p>
|
||
{# Stats row #}
|
||
<div class="flex flex-wrap items-center justify-center gap-x-8 gap-y-3 text-brand-navy">
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-2xl font-black grad-text">12</span>
|
||
<span class="text-xs font-medium text-brand-navy/60 uppercase tracking-wider">fonctions · modules</span>
|
||
</div>
|
||
<div class="w-px h-6 bg-brand-navy/15 hidden sm:block" aria-hidden="true"></div>
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-2xl font-black grad-text">99+</span>
|
||
<span class="text-xs font-medium text-brand-navy/60 uppercase tracking-wider">langues</span>
|
||
</div>
|
||
<div class="w-px h-6 bg-brand-navy/15 hidden sm:block" aria-hidden="true"></div>
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-2xl font-black grad-text">0</span>
|
||
<span class="text-xs font-medium text-brand-navy/60 uppercase tracking-wider">cloud</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# ── DASHBOARD HOLOGRAPHIQUE — 2 colonnes (phone center + IA right) ── #}
|
||
<div x-data="dictiaDashboard()" x-init="init()"
|
||
class="w-full flex flex-col lg:flex-row lg:justify-center lg:items-start gap-6 lg:gap-10 relative">
|
||
|
||
{# Connecting line SVG (phone -> IA panel, desktop only) #}
|
||
<div class="dictia-connecting-line-wrap absolute pointer-events-none hidden lg:block"
|
||
style="top:200px;left:50%;transform:translateX(-50%);width:160px;height:2px;z-index:1;"
|
||
aria-hidden="true">
|
||
<svg width="160" height="2" viewBox="0 0 160 2" preserveAspectRatio="none">
|
||
<defs>
|
||
<linearGradient id="dictia-connecting-grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||
<stop offset="0%" stop-color="#06b6d4" stop-opacity="0.6"/>
|
||
<stop offset="100%" stop-color="#c026d3" stop-opacity="0.6"/>
|
||
</linearGradient>
|
||
</defs>
|
||
<line x1="0" y1="1" x2="160" y2="1" stroke="url(#dictia-connecting-grad)"
|
||
stroke-width="1.5" stroke-dasharray="4 4" class="dictia-connecting-line"/>
|
||
</svg>
|
||
</div>
|
||
|
||
{# ─────────── ZONE CENTER : Phone container ─────────── #}
|
||
<div class="flex flex-col items-center gap-3 lg:w-[280px] flex-shrink-0 relative z-10">
|
||
|
||
{# Phone shell (bezel, glow ring, drop shadow) — LARGEUR/HAUTEUR FIXES pour stabilité du cadre #}
|
||
<div class="relative mx-auto dictia-phone-wrap" style="width: 280px; max-width: 100%;">
|
||
|
||
{# External glow ring (pulse) — STATIC brand-b1 halo, ne change PAS selon mode pour stabilité visuelle du contour #}
|
||
<div class="absolute -inset-2 pointer-events-none dictia-phone-glow-ring"
|
||
style="border-radius: 52px; background: radial-gradient(ellipse 80% 90% at 50% 50%, rgba(37,99,235,0.18) 0%, transparent 60%);"
|
||
aria-hidden="true"></div>
|
||
|
||
{# Phone shell — bezel/border/shadow STATIQUES (couleur ne change jamais entre modes — comme un vrai téléphone) #}
|
||
<div class="dictia-phone-shell flex flex-col overflow-hidden relative"
|
||
style="width: 280px; height: 580px; border: 1.5px solid rgba(255,255,255,0.10); background: rgba(8,12,24,0.88); box-shadow: 0 30px 80px -20px rgba(0,0,0,0.6), 0 12px 32px -8px rgba(37,99,235,0.20), inset 0 0 32px rgba(255,255,255,0.04);">
|
||
|
||
{# Inner screen seam — effet "écran encastré dans le bezel" #}
|
||
<div class="dictia-phone-screen-seam" aria-hidden="true"></div>
|
||
|
||
{# Ambient overlay — STATIC subtle (pas de tint par mode) #}
|
||
<div class="absolute inset-0 pointer-events-none"
|
||
style="border-radius: 44px; background-color: rgba(255,255,255,0.015);"
|
||
aria-hidden="true"></div>
|
||
|
||
{# Notch / Dynamic Island avec mini speaker grille (3 dots) #}
|
||
<div class="dictia-notch absolute left-1/2 -translate-x-1/2 z-30 flex items-center justify-center gap-1"
|
||
style="top:8px;width:78px;height:18px;border-radius:12px;background:rgba(0,0,0,0.85);box-shadow:inset 0 1px 2px rgba(0,0,0,0.5);"
|
||
aria-hidden="true">
|
||
{# Speaker grille (3 dots) #}
|
||
<span class="block rounded-full" style="width:3px;height:3px;background:rgba(255,255,255,0.10);"></span>
|
||
<span class="block rounded-full" style="width:3px;height:3px;background:rgba(255,255,255,0.12);"></span>
|
||
<span class="block rounded-full" style="width:3px;height:3px;background:rgba(255,255,255,0.10);"></span>
|
||
{# Camera dot #}
|
||
<span class="block rounded-full ml-1" style="width:5px;height:5px;background:rgba(255,255,255,0.18);box-shadow:inset 0 0 1px rgba(0,0,0,0.5);"></span>
|
||
</div>
|
||
|
||
{# Status bar mobile-style (9:41 + signals) — vraies icônes SVG device-like #}
|
||
<div class="dictia-statusbar relative z-20 flex items-center justify-between px-6 font-mono text-[10px] font-semibold"
|
||
style="height:34px;padding-top:12px;color:rgba(255,255,255,0.65);"
|
||
aria-hidden="true">
|
||
<span class="font-bold">9:41</span>
|
||
<div class="flex items-center gap-1.5">
|
||
{# Signal — 4 bars croissantes, look device authentique #}
|
||
<svg viewBox="0 0 16 10" width="13" height="9" fill="currentColor">
|
||
<rect x="0" y="7" width="2.5" height="3" rx="0.5"/>
|
||
<rect x="3.5" y="5" width="2.5" height="5" rx="0.5"/>
|
||
<rect x="7" y="2.5" width="2.5" height="7.5" rx="0.5"/>
|
||
<rect x="10.5" y="0" width="2.5" height="10" rx="0.5"/>
|
||
</svg>
|
||
{# Wifi — 3 arcs concentriques + dot, look iOS authentique #}
|
||
<svg viewBox="0 0 16 12" width="14" height="10" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round">
|
||
<path d="M1 4.5 Q8 -1.5 15 4.5"/>
|
||
<path d="M3.2 7 Q8 3.2 12.8 7"/>
|
||
<path d="M5.4 9.5 Q8 7.5 10.6 9.5"/>
|
||
<circle cx="8" cy="11" r="0.9" fill="currentColor"/>
|
||
</svg>
|
||
{# Battery 80% — fill réel à 80% pour authenticité #}
|
||
<svg viewBox="0 0 22 10" width="18" height="9" fill="none">
|
||
<rect x="0.5" y="0.5" width="18" height="9" rx="2" stroke="currentColor" stroke-opacity="0.6" stroke-width="0.8" fill="none"/>
|
||
<rect x="19.5" y="3.5" width="1.5" height="3" rx="0.5" fill="currentColor" fill-opacity="0.6"/>
|
||
<rect x="2" y="2" width="14" height="6" rx="1" fill="currentColor"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
|
||
{# TOP : Logo + Mic pulsing (with sound waves) — hauteur fixe 96px #}
|
||
<div class="flex flex-col items-center justify-center gap-2 relative z-10 flex-shrink-0"
|
||
style="height: 96px; padding-top: 12px; border-bottom: 1px solid rgba(255,255,255,0.06);">
|
||
<img src="/static/images/dictia-logo-nom.png" alt="DictIA"
|
||
style="width: 92px; height: 28px; object-fit: contain; opacity: 0.85;">
|
||
<div class="relative" style="width:36px;height:36px;">
|
||
{# 3 sound rings emanating #}
|
||
<span class="dictia-sound-ring r1" :style="`color: ${activeColor}; width:24px; height:24px;`" aria-hidden="true"></span>
|
||
<span class="dictia-sound-ring r2" :style="`color: ${activeColor}; width:24px; height:24px;`" aria-hidden="true"></span>
|
||
<span class="dictia-sound-ring r3" :style="`color: ${activeColor}; width:24px; height:24px;`" aria-hidden="true"></span>
|
||
<span class="dictia-mic-pulse absolute" style="top:50%;left:50%;transform:translate(-50%,-50%);" :style="`color: ${activeColor};`" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:22px;height:22px;">
|
||
<rect x="9" y="2" width="6" height="12" rx="3"/>
|
||
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
||
<line x1="12" y1="19" x2="12" y2="23"/>
|
||
<line x1="8" y1="23" x2="16" y2="23"/>
|
||
</svg>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{# MIDDLE : 6 modes — hauteur fixe 374px, overflow hidden strict #}
|
||
<div class="dictia-phone-middle relative z-10 flex-shrink-0" style="height: 374px; overflow: hidden;"
|
||
role="status" aria-live="polite" aria-atomic="true">
|
||
|
||
{# Mode 1 : Transcription (header REC + waveform + upload bar + words) #}
|
||
<template x-if="displayMode === 1">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="trModeData()" x-init="init()">
|
||
{# Header bar (file + REC) — compact mic icon + filename + REC dot #}
|
||
<div class="flex items-center justify-between px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(6,182,212,0.06);border-bottom:1px solid rgba(6,182,212,0.12);">
|
||
<div class="flex items-center gap-1.5 min-w-0">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-2.5 h-2.5 text-brand-b2 shrink-0" aria-hidden="true">
|
||
<rect x="9" y="2" width="6" height="12" rx="3"/>
|
||
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
||
<line x1="12" y1="19" x2="12" y2="23"/>
|
||
</svg>
|
||
<span class="font-mono text-[10px] text-white/60 truncate">reunion-jan14.mp3</span>
|
||
</div>
|
||
<div class="flex items-center gap-1 shrink-0">
|
||
<span class="dictia-rec-dot block rounded-full" style="width:5px;height:5px;background:#ef4444;"></span>
|
||
<span class="font-mono text-[9px] font-bold tracking-widest" style="color:rgba(239,68,68,0.90);">REC</span>
|
||
</div>
|
||
</div>
|
||
|
||
{# Waveform animée (16 bars régulières — pattern symétrique) #}
|
||
<div class="flex items-end justify-center gap-0.5 px-3 py-2 flex-shrink-0" style="height:28px;" aria-hidden="true">
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:8px;animation-delay:0s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:12px;animation-delay:0.08s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:18px;animation-delay:0.16s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:14px;animation-delay:0.24s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:20px;animation-delay:0.32s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:16px;animation-delay:0.4s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:22px;animation-delay:0.48s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:18px;animation-delay:0.56s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:22px;animation-delay:0.48s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:16px;animation-delay:0.4s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:20px;animation-delay:0.32s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:14px;animation-delay:0.24s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:18px;animation-delay:0.16s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:12px;animation-delay:0.08s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:8px;animation-delay:0s;"></div>
|
||
<div class="dictia-wave-bar bg-brand-b2" style="width:2px;height:6px;animation-delay:0.04s;"></div>
|
||
</div>
|
||
|
||
<div x-show="phase === 'upload'" class="flex-1 flex flex-col items-center justify-center gap-3 px-4 overflow-hidden">
|
||
<div class="flex flex-col items-center gap-2">
|
||
{# File card MP3 — design device pro avec corner fold #}
|
||
<div class="flex flex-col items-center justify-center relative"
|
||
style="width:44px;height:54px;background-color:rgba(6,182,212,0.12);border:1.5px solid rgba(6,182,212,0.45);border-radius:6px;">
|
||
<div class="absolute top-0 right-0" style="width:0;height:0;border-style:solid;border-width:0 8px 8px 0;border-color:transparent rgba(0,0,0,0.45) transparent transparent;"></div>
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-brand-b2" style="width:14px;height:14px;" aria-hidden="true">
|
||
<rect x="9" y="2" width="6" height="12" rx="3"/>
|
||
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
||
<line x1="12" y1="19" x2="12" y2="23"/>
|
||
</svg>
|
||
<span class="font-mono text-[9px] font-bold tracking-wider mt-1" style="color:rgba(6,182,212,0.80);">MP3</span>
|
||
</div>
|
||
</div>
|
||
<div class="w-full flex flex-col gap-1.5">
|
||
<div class="flex justify-between items-center">
|
||
<span class="font-mono text-[10px]" style="color:rgba(6,182,212,0.75);" x-text="progress < 100 ? 'Envoi en cours…' : 'Prêt ✓'"></span>
|
||
<span class="font-mono text-[10px] font-semibold" style="color:rgba(6,182,212,0.65);"><span x-text="progress"></span>%</span>
|
||
</div>
|
||
{# Track gris fin + fill brand-b2 glow #}
|
||
<div class="w-full rounded-full overflow-hidden relative" style="height:4px;background-color:rgba(255,255,255,0.06);box-shadow:inset 0 0 2px rgba(0,0,0,0.4);">
|
||
<div class="h-full rounded-full bg-brand-b2" :style="`width: ${progress}%; box-shadow: 0 0 8px rgba(6,182,212,0.7); transition: width 60ms linear;`"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{# Transcript zone — scroll interne avec fade bottom #}
|
||
<div x-show="phase === 'transcribing'" class="flex-1 p-3 dictia-mode-scroll dictia-fade-bottom flex flex-col justify-start" x-ref="transcriptBox">
|
||
<p class="font-mono leading-relaxed break-words text-[10px] rounded"
|
||
style="color:rgba(6,182,212,0.92);background:rgba(6,182,212,0.04);padding:6px 8px;border-left:2px solid rgba(6,182,212,0.4);">
|
||
<span x-text="words.slice(0, n + 1).join(' ')"></span><span class="dictia-blink ml-px inline-block align-middle bg-brand-b2" style="width:3px;height:10px;"> </span>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 2 : Diarisation (conversation Sophie/Marc/Julie) #}
|
||
<template x-if="displayMode === 2">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="diaModeData()" x-init="init()">
|
||
{# Conversation header — 3 avatars empilés 24×24 avec bordure white/15 #}
|
||
<div class="flex items-center justify-between px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(37,99,235,0.06);border-bottom:1px solid rgba(37,99,235,0.12);">
|
||
<div class="flex items-center gap-2">
|
||
<div class="flex">
|
||
<div class="rounded-full flex items-center justify-center text-[10px] font-bold text-white"
|
||
style="width:18px;height:18px;background:linear-gradient(135deg,#06b6d4,#2563eb);border:1.5px solid rgba(8,12,24,0.9);">S</div>
|
||
<div class="rounded-full flex items-center justify-center text-[10px] font-bold text-white"
|
||
style="width:18px;height:18px;background:linear-gradient(135deg,#2563eb,#06b6d4);border:1.5px solid rgba(8,12,24,0.9);margin-left:-6px;">M</div>
|
||
<div class="rounded-full flex items-center justify-center text-[10px] font-bold text-white"
|
||
style="width:18px;height:18px;background:linear-gradient(135deg,#c026d3,#2563eb);border:1.5px solid rgba(8,12,24,0.9);margin-left:-6px;">J</div>
|
||
</div>
|
||
<span class="font-mono text-[10px] text-white/65">Réunion · 3 participants</span>
|
||
</div>
|
||
<span class="dictia-rec-dot block rounded-full" style="width:5px;height:5px;background:#10b981;"></span>
|
||
</div>
|
||
<div class="flex-1 flex flex-col justify-end gap-1.5 p-2 dictia-mode-scroll dictia-fade-bottom">
|
||
<template x-for="(msg, i) in visible" :key="`${cycle}-${startIdx + i}`">
|
||
<div class="dictia-msg-in flex items-start gap-1.5">
|
||
<div class="rounded-full flex items-center justify-center text-[10px] font-bold shrink-0 mt-0.5"
|
||
style="width:24px;height:24px;"
|
||
:style="`background:linear-gradient(135deg, ${msg.c}33, ${msg.c}22); border: 1.5px solid ${msg.c}66; color: ${msg.c};`"
|
||
x-text="msg.s.charAt(0)"></div>
|
||
<div class="rounded px-2 py-1.5 flex-1 relative"
|
||
style="max-width:80%;"
|
||
:style="`background-color: ${msg.c}10; border: 1px solid ${msg.c}28; box-shadow: 0 1px 4px rgba(0,0,0,0.15);`">
|
||
<div class="flex items-baseline justify-between gap-2 mb-0.5">
|
||
<p class="text-[10px] font-bold leading-none" :style="`color: ${msg.c};`" x-text="msg.s"></p>
|
||
<span class="text-[9px] font-mono" style="color:rgba(255,255,255,0.30);" x-text="`09:0${i+1}`"></span>
|
||
</div>
|
||
<p class="text-[10px] font-mono leading-snug" style="color:rgba(255,255,255,0.80);" x-text="msg.t"></p>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
{# Typing indicator (3 dots pulse en boucle) #}
|
||
<div class="flex items-center gap-1.5 px-1" x-show="shownCount < CONVO.length" aria-hidden="true">
|
||
<div class="rounded-full" style="width:24px;height:24px;background:rgba(255,255,255,0.05);border:1px dashed rgba(255,255,255,0.15);"></div>
|
||
<div class="flex items-center gap-1 rounded px-2 py-1.5"
|
||
style="background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);">
|
||
<span class="dictia-typing-dot block rounded-full" style="width:4px;height:4px;background:rgba(255,255,255,0.5);animation-delay:0s;"></span>
|
||
<span class="dictia-typing-dot block rounded-full" style="width:4px;height:4px;background:rgba(255,255,255,0.5);animation-delay:0.2s;"></span>
|
||
<span class="dictia-typing-dot block rounded-full" style="width:4px;height:4px;background:rgba(255,255,255,0.5);animation-delay:0.4s;"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 3 : 99+ langues (grille 100 codes — ripple depuis centre, highlight aléatoire) #}
|
||
<template x-if="displayMode === 3">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="langModeData()">
|
||
{# Header DÉTECTION AUTOMATIQUE #}
|
||
<div class="flex items-center justify-center gap-2 py-2 flex-shrink-0"
|
||
style="background:rgba(6,182,212,0.06);border-bottom:1px solid rgba(6,182,212,0.12);">
|
||
<span class="dictia-dot-pulse block rounded-full bg-brand-b2" style="width:5px;height:5px;"></span>
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:rgba(6,182,212,0.90);">DÉTECTION AUTOMATIQUE</span>
|
||
</div>
|
||
{# Scroll interne pour les 100 codes — fade bottom suggère continuation #}
|
||
<div class="flex-1 dictia-mode-scroll dictia-fade-bottom" style="padding:8px 6px;">
|
||
<div class="grid content-start" style="grid-template-columns: repeat(7, 1fr); gap: 4px;">
|
||
<template x-for="(lang, i) in LANGS" :key="lang">
|
||
<span class="font-mono font-bold text-center rounded dictia-spring text-[10px]"
|
||
:class="HIGHLIGHTS.includes(i) ? 'dictia-lang-highlight' : ''"
|
||
:style="`line-height:18px;background-color:${LANG_COLORS[i % LANG_COLORS.length]}14;border:1px solid ${LANG_COLORS[i % LANG_COLORS.length]}30;color:${LANG_COLORS[i % LANG_COLORS.length]};animation-delay:${rippleDelay(i)}ms;`"
|
||
x-text="lang"></span>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
{# Counter live en bas #}
|
||
<div class="flex items-center justify-between px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(6,182,212,0.04);border-top:1px solid rgba(6,182,212,0.10);">
|
||
<span class="font-mono dictia-fade-y text-[9px] tracking-wide" style="color:rgba(6,182,212,0.75);animation-delay:1.4s;">
|
||
FR · EN · ES · DE · ZH · JA · AR
|
||
</span>
|
||
<span class="font-mono font-bold dictia-fade-y text-[10px] tracking-wider text-brand-b2" style="animation-delay:1.4s;">
|
||
99+ langues détectées
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 4 : Exports (grid 4x2 + files détaillés + checkmark) #}
|
||
<template x-if="displayMode === 4">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="expModeData()">
|
||
{# Header subtle #}
|
||
<div class="flex items-center justify-between px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(37,99,235,0.06);border-bottom:1px solid rgba(37,99,235,0.12);">
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:rgba(37,99,235,0.90);">EXPORTS DISPONIBLES</span>
|
||
<span class="font-mono text-[9px] text-brand-b1/60">7 formats</span>
|
||
</div>
|
||
|
||
{# Grid 4×2 — staggered drop animation #}
|
||
<div class="grid grid-cols-4 gap-2 p-3 flex-1 content-center justify-items-center overflow-hidden">
|
||
<template x-for="(f, i) in FILE_TYPES" :key="f.ext">
|
||
<div class="dictia-spring-y" :style="`animation-delay:${i * 90}ms;`">
|
||
<div class="flex flex-col items-center justify-end gap-0.5 relative"
|
||
style="width:42px;height:50px;border-radius:4px;"
|
||
:style="`background-color:${f.bg}CC;border:1.5px solid ${f.bg};box-shadow:0 4px 12px ${f.bg}40;`">
|
||
<div class="absolute top-0 right-0" style="width:0;height:0;border-style:solid;border-width:0 9px 9px 0;border-color:transparent rgba(0,0,0,0.45) transparent transparent;"></div>
|
||
{# Mini "page" lines #}
|
||
<div class="absolute top-2 left-1.5 right-3 flex flex-col gap-0.5" aria-hidden="true">
|
||
<div :style="`height:1px;background:${f.fg}55;width:80%;`"></div>
|
||
<div :style="`height:1px;background:${f.fg}40;width:65%;`"></div>
|
||
<div :style="`height:1px;background:${f.fg}40;width:75%;`"></div>
|
||
</div>
|
||
<span class="text-[11px] font-black leading-none" :style="`color:${f.fg};margin-bottom:6px;`" x-text="f.sym"></span>
|
||
<span class="text-[9px] font-mono font-bold leading-none mb-1.5" :style="`color:${f.fg}BB;`" x-text="f.ext"></span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
{# Subtitle dynamique avec checkmark — vert sémantique conservé pour status "ready" #}
|
||
<div class="flex items-center justify-center gap-2 px-3 py-2 dictia-fade-y flex-shrink-0"
|
||
style="background:rgba(16,185,129,0.06);border-top:1px solid rgba(16,185,129,0.14);animation-delay:0.9s;">
|
||
<span class="inline-flex items-center justify-center rounded-full" style="width:13px;height:13px;background:#10b981;">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="rgba(8,12,24,0.9)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" style="width:8px;height:8px;" aria-hidden="true">
|
||
<polyline points="20 6 9 17 4 12"/>
|
||
</svg>
|
||
</span>
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:#10b981;">7 FORMATS PRÊTS</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 5 : Users (counter centré + avatars colors variés + connecting lines) #}
|
||
<template x-if="displayMode === 5">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="usersModeData()" x-init="init()">
|
||
{# Header counter — gros chiffre Inter font-black #}
|
||
<div class="flex items-center justify-center gap-2 py-2 flex-shrink-0"
|
||
style="background:rgba(192,38,211,0.06);border-bottom:1px solid rgba(192,38,211,0.12);">
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:rgba(192,38,211,0.85);">UTILISATEURS</span>
|
||
<span class="font-black text-base text-brand-b3 leading-none" x-text="count.toString().padStart(2, '0')"></span>
|
||
<span class="font-mono text-[10px]" style="color:rgba(192,38,211,0.50);">→ ∞</span>
|
||
</div>
|
||
|
||
{# Avatar grid with connecting lines SVG #}
|
||
<div class="flex-1 relative p-3 flex flex-wrap gap-2 items-center justify-center content-center overflow-hidden">
|
||
{# SVG connecting lines (brand b1/b2/b3 only) — opacité subtle #}
|
||
<svg class="absolute inset-0 pointer-events-none" width="100%" height="100%" aria-hidden="true" style="opacity:0.18;">
|
||
<line x1="20%" y1="30%" x2="50%" y2="50%" stroke="#c026d3" stroke-width="0.5" stroke-dasharray="2 2"/>
|
||
<line x1="80%" y1="30%" x2="50%" y2="50%" stroke="#06b6d4" stroke-width="0.5" stroke-dasharray="2 2"/>
|
||
<line x1="20%" y1="70%" x2="50%" y2="50%" stroke="#c026d3" stroke-width="0.5" stroke-dasharray="2 2"/>
|
||
<line x1="80%" y1="70%" x2="50%" y2="50%" stroke="#2563eb" stroke-width="0.5" stroke-dasharray="2 2"/>
|
||
</svg>
|
||
<template x-for="i in count" :key="`${cycle}-${i}`">
|
||
<div class="rounded-full flex items-center justify-center relative z-10"
|
||
:class="i === count ? 'dictia-spring' : ''"
|
||
:style="`width:24px;height:24px;background-color:${USER_COLORS[(i-1) % USER_COLORS.length]}22;border:1.5px solid ${USER_COLORS[(i-1) % USER_COLORS.length]}77;`">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:11px;height:11px;" :style="`color:${USER_COLORS[(i-1) % USER_COLORS.length]};`" aria-hidden="true">
|
||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||
<circle cx="12" cy="7" r="4"/>
|
||
</svg>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 6 : Share (breadcrumb + toolbar + rows structurées) #}
|
||
<template x-if="displayMode === 6">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden">
|
||
{# Breadcrumb compact 12px max + toolbar 4 icons #}
|
||
<div class="flex items-center gap-1.5 px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(6,182,212,0.06);border-bottom:1px solid rgba(6,182,212,0.12);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-brand-b2 shrink-0" style="width:9px;height:9px;" aria-hidden="true">
|
||
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||
</svg>
|
||
<span class="font-mono text-[10px] text-white/55">Mes dossiers</span>
|
||
<span class="text-[9px] text-white/30">›</span>
|
||
<span class="font-mono text-[10px] font-bold text-brand-b2">Réunions</span>
|
||
{# Toolbar mini 4 icons #}
|
||
<div class="flex items-center gap-1.5 ml-auto" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-white/40" style="width:10px;height:10px;">
|
||
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||
</svg>
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-white/40" style="width:10px;height:10px;">
|
||
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>
|
||
</svg>
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-white/40" style="width:10px;height:10px;">
|
||
<path d="M3 6h18M3 12h18M3 18h18"/>
|
||
</svg>
|
||
<svg viewBox="0 0 24 24" fill="currentColor" class="text-white/40" style="width:10px;height:10px;">
|
||
<circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex-1 flex flex-col gap-2 dictia-mode-scroll dictia-fade-bottom" style="padding:8px;">
|
||
{# Folders #}
|
||
<div class="flex gap-1 flex-wrap">
|
||
<template x-for="(f, fi) in [{name:'Réunions',color:'#06b6d4',count:12},{name:'Entretiens',color:'#2563eb',count:7},{name:'Formations',color:'#c026d3',count:24}]" :key="f.name">
|
||
<div class="flex items-center gap-1 rounded dictia-fade-y"
|
||
:style="`background-color:${f.color}18;border:1px solid ${f.color}35;padding:3px 6px;animation-delay:${fi * 100}ms;`">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:9px;height:9px;" :style="`color:${f.color};`" aria-hidden="true">
|
||
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||
</svg>
|
||
<span class="font-mono text-[9px] font-semibold" :style="`color:${f.color};`" x-text="f.name"></span>
|
||
<span class="font-mono text-[9px]" :style="`color:${f.color}88;`" x-text="f.count"></span>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
{# Tags pills #}
|
||
<div class="flex flex-wrap gap-1">
|
||
<template x-for="(tag, ti) in ['#RH','#Direction','#Urgent','#Archivé','#Suivi','#Confidentiel','#2024']" :key="tag">
|
||
<span class="rounded-full dictia-spring font-mono text-[9px] text-brand-b2"
|
||
style="padding:2px 6px;background-color:rgba(6,182,212,0.10);border:1px solid rgba(6,182,212,0.25);"
|
||
:style="`animation-delay:${350 + ti * 70}ms;`"
|
||
x-text="tag"></span>
|
||
</template>
|
||
</div>
|
||
{# File rows structurées — hover highlight #}
|
||
<div class="flex flex-col gap-1">
|
||
<template x-for="(file, fi2) in [{name:'CR-Réunion-Jan14',folder:'Réunions',color:'#06b6d4'},{name:'Entretien-Sophie',folder:'Entretiens',color:'#2563eb'},{name:'Formation-RGPD',folder:'Formations',color:'#c026d3'}]" :key="file.name">
|
||
<div class="dictia-share-row group flex items-center gap-2 rounded dictia-fade-x cursor-pointer"
|
||
:style="`background-color:${file.color}0C;border:1px solid ${file.color}22;padding:5px 8px;animation-delay:${650 + fi2 * 120}ms;transition:background-color 0.2s;`">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0" style="width:10px;height:10px;" :style="`color:${file.color};`" 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"/>
|
||
</svg>
|
||
<div class="flex-1 min-w-0">
|
||
<p class="truncate font-mono text-[10px] text-white/80" x-text="file.name"></p>
|
||
</div>
|
||
<span class="rounded font-mono shrink-0 text-[9px]"
|
||
style="padding:2px 5px;"
|
||
:style="`background:${file.color}18;color:${file.color};border:1px solid ${file.color}33;`"
|
||
x-text="file.folder"></span>
|
||
<span class="shrink-0" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="currentColor" style="width:10px;height:10px;" :style="`color:${file.color}77;`">
|
||
<circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/>
|
||
</svg>
|
||
</span>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
<p class="text-center font-mono dictia-fade-y px-3 py-2 flex-shrink-0 text-[9px] tracking-wide"
|
||
style="color:rgba(6,182,212,0.70);background:rgba(6,182,212,0.04);border-top:1px solid rgba(6,182,212,0.10);animation-delay:1.2s;">
|
||
partage sécurisé · recherche plein texte
|
||
</p>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 0 : IA chat (Mistral 7B local Q&R typing) — visible si user clique sur IA dans grid #}
|
||
<template x-if="displayMode === 0">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="iaModeData()" x-init="init()">
|
||
<div class="flex items-center justify-center gap-2 py-2 flex-shrink-0"
|
||
style="border-bottom: 1px solid rgba(192,38,211,0.14); background-color: rgba(192,38,211,0.05);">
|
||
<span class="rounded-full flex-shrink-0 dictia-dot-pulse block" style="width:6px;height:6px;background-color:#10b981;"></span>
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:rgba(192,38,211,0.90);">MISTRAL 7B · LOCAL</span>
|
||
</div>
|
||
<div class="flex-1 flex flex-col justify-end gap-2 p-2 dictia-mode-scroll dictia-fade-bottom">
|
||
<template x-for="(msg, mi) in msgs" :key="mi">
|
||
<div class="dictia-fade-y" :class="msg.role === 'user' ? 'flex justify-end' : 'flex justify-start'">
|
||
<div class="rounded px-2 py-1.5" style="max-width:88%;"
|
||
:style="msg.role === 'user' ? 'background-color:rgba(255,255,255,0.07);border:1px solid rgba(255,255,255,0.12);' : 'background-color:rgba(192,38,211,0.12);border:1px solid rgba(192,38,211,0.30);'">
|
||
<p class="font-mono leading-relaxed whitespace-pre-line text-[10px]"
|
||
:style="msg.role === 'user' ? 'color:rgba(255,255,255,0.70);' : 'color:rgba(192,38,211,0.95);'">
|
||
<span x-text="msg.text"></span><span x-show="msg.role === 'bot' && mi === msgs.length - 1" class="dictia-blink ml-px text-brand-b3">▌</span>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<div class="flex items-center justify-center gap-1.5 py-2 flex-shrink-0" style="border-top:1px solid rgba(192,38,211,0.10);background:rgba(192,38,211,0.03);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="shrink-0" style="width:9px;height:9px;color:rgba(16,185,129,0.85);" aria-hidden="true">
|
||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||
</svg>
|
||
<span class="font-mono text-[9px] tracking-wide" style="color:rgba(16,185,129,0.85);">0 donnée envoyée au cloud</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 7 : Recording live (waveform live + minuteur + 3 boutons mic/syst/combiné) — Catégorie Capture #}
|
||
<template x-if="displayMode === 7">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="recModeData()" x-init="init()">
|
||
<div class="flex items-center justify-between px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(6,182,212,0.06);border-bottom:1px solid rgba(6,182,212,0.12);">
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:rgba(6,182,212,0.90);">ENREGISTREMENT LIVE</span>
|
||
<div class="flex items-center gap-1">
|
||
<span class="dictia-rec-dot block rounded-full" style="width:5px;height:5px;background:#ef4444;"></span>
|
||
<span class="font-mono text-[9px] font-bold tracking-widest" style="color:rgba(239,68,68,0.90);">REC</span>
|
||
</div>
|
||
</div>
|
||
{# Minuteur central + waveform live #}
|
||
<div class="flex flex-col items-center justify-center gap-2 flex-1 px-3">
|
||
<p class="font-mono font-black text-2xl text-white tabular-nums leading-none" x-text="time"></p>
|
||
<p class="font-mono text-[9px] uppercase tracking-widest" style="color:rgba(6,182,212,0.65);">Microphone + Système</p>
|
||
{# Waveform 24 barres dynamiques (pattern live aléatoire CSS-only) #}
|
||
<div class="flex items-center justify-center gap-0.5 mt-1" style="height:36px;" aria-hidden="true">
|
||
<template x-for="(h, bi) in BARS" :key="bi">
|
||
<div class="dictia-wave-bar bg-brand-b2"
|
||
:style="`width:2px;height:${h}px;animation-delay:${bi * 30}ms;`"></div>
|
||
</template>
|
||
</div>
|
||
{# Estimation taille fichier #}
|
||
<p class="font-mono text-[9px] mt-2" style="color:rgba(6,182,212,0.55);">~<span x-text="size"></span> Mo · auto-save 5 s</p>
|
||
</div>
|
||
{# 3 boutons sources (rouge mic / bleu système / violet combiné) — selon manuel #}
|
||
<div class="flex items-center justify-center gap-2 px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(8,12,24,0.5);border-top:1px solid rgba(255,255,255,0.06);">
|
||
<span class="rounded-full flex items-center justify-center" style="width:18px;height:18px;background:rgba(239,68,68,0.30);border:1px solid rgba(239,68,68,0.55);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:9px;height:9px;" aria-hidden="true">
|
||
<rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
||
</svg>
|
||
</span>
|
||
<span class="rounded-full flex items-center justify-center" style="width:18px;height:18px;background:rgba(37,99,235,0.30);border:1px solid rgba(37,99,235,0.55);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:9px;height:9px;" aria-hidden="true">
|
||
<rect x="3" y="4" width="18" height="14" rx="2"/><line x1="9" y1="22" x2="15" y2="22"/>
|
||
</svg>
|
||
</span>
|
||
<span class="rounded-full flex items-center justify-center dictia-spring" style="width:22px;height:22px;background:rgba(192,38,211,0.30);border:1.5px solid rgba(192,38,211,0.65);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="#c026d3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:11px;height:11px;" aria-hidden="true">
|
||
<path d="M12 2v8M5 7l7 7 7-7"/>
|
||
</svg>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 8 : Recherche sémantique (search bar + résultats highlight) — Catégorie Capture #}
|
||
<template x-if="displayMode === 8">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="searchModeData()" x-init="init()">
|
||
<div class="flex items-center justify-center gap-2 py-2 flex-shrink-0"
|
||
style="background:rgba(6,182,212,0.06);border-bottom:1px solid rgba(6,182,212,0.12);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:11px;height:11px;color:rgba(6,182,212,0.90);" aria-hidden="true">
|
||
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||
</svg>
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:rgba(6,182,212,0.90);">RECHERCHE IA SÉMANTIQUE</span>
|
||
</div>
|
||
{# Search bar simulée (typing query) #}
|
||
<div class="px-3 py-2 flex-shrink-0">
|
||
<div class="rounded flex items-center gap-2 px-2 py-1.5"
|
||
style="background:rgba(255,255,255,0.06);border:1px solid rgba(6,182,212,0.30);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:9px;height:9px;color:rgba(6,182,212,0.70);" aria-hidden="true">
|
||
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||
</svg>
|
||
<p class="font-mono text-[10px] flex-1 truncate" style="color:rgba(255,255,255,0.85);">
|
||
<span x-text="query"></span><span class="dictia-blink ml-px inline-block align-middle bg-brand-b2" style="width:2px;height:9px;"> </span>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
{# Résultats : 3 transcriptions matchées avec phrase highlight #}
|
||
<div class="flex-1 dictia-mode-scroll dictia-fade-bottom flex flex-col gap-1.5 px-3 pb-2">
|
||
<template x-for="(r, ri) in results" :key="ri">
|
||
<div class="rounded dictia-fade-y"
|
||
:style="`background-color:${r.c}10;border:1px solid ${r.c}30;padding:6px 8px;animation-delay:${ri * 280 + 600}ms;`">
|
||
<div class="flex items-center gap-1.5 mb-1">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" :style="`color:${r.c};width:9px;height:9px;`" 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"/>
|
||
</svg>
|
||
<span class="font-mono text-[9px] font-bold" :style="`color:${r.c};`" x-text="r.title"></span>
|
||
<span class="font-mono text-[9px] ml-auto" style="color:rgba(255,255,255,0.40);" x-text="r.score + ' %'"></span>
|
||
</div>
|
||
<p class="font-mono text-[9px] leading-snug" style="color:rgba(255,255,255,0.75);">
|
||
… <span :style="`background:${r.c}28;color:${r.c};padding:0 2px;border-radius:2px;`" x-text="r.match"></span> …
|
||
</p>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 9 : Résumé + extraction d'événements (panel structuré décisions/actions/ICS) — Catégorie IA #}
|
||
<template x-if="displayMode === 9">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="summaryModeData()" x-init="init()">
|
||
<div class="flex items-center justify-between px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(37,99,235,0.06);border-bottom:1px solid rgba(37,99,235,0.12);">
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:rgba(37,99,235,0.90);">RÉSUMÉ EXÉCUTIF</span>
|
||
<span class="font-mono text-[9px] text-brand-b1/60">Mistral Nemo 12B</span>
|
||
</div>
|
||
<div class="flex-1 dictia-mode-scroll dictia-fade-bottom flex flex-col gap-2 px-3 py-2">
|
||
{# Décisions #}
|
||
<div class="dictia-fade-y" style="animation-delay:200ms;">
|
||
<p class="font-mono text-[9px] font-bold uppercase tracking-wider mb-1" style="color:rgba(37,99,235,0.85);">Décisions</p>
|
||
<template x-for="(d, di) in decisions.slice(0, dShown)" :key="di">
|
||
<div class="flex items-start gap-1.5 mb-0.5 dictia-fade-x">
|
||
<span class="text-brand-b1 leading-none mt-0.5" aria-hidden="true">•</span>
|
||
<p class="text-[10px] font-mono leading-snug" style="color:rgba(255,255,255,0.85);" x-text="d"></p>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
{# Actions à suivre #}
|
||
<div class="dictia-fade-y" style="animation-delay:900ms;" x-show="dShown >= decisions.length">
|
||
<p class="font-mono text-[9px] font-bold uppercase tracking-wider mb-1" style="color:rgba(192,38,211,0.85);">Actions à suivre</p>
|
||
<template x-for="(a, ai) in actions.slice(0, aShown)" :key="ai">
|
||
<div class="flex items-start gap-1.5 mb-0.5 dictia-fade-x">
|
||
<span class="rounded flex items-center justify-center mt-0.5 flex-shrink-0" style="width:9px;height:9px;background:rgba(192,38,211,0.30);border:1px solid rgba(192,38,211,0.55);" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="#c026d3" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="width:5px;height:5px;"><polyline points="20 6 9 17 4 12"/></svg>
|
||
</span>
|
||
<p class="text-[10px] font-mono leading-snug" style="color:rgba(255,255,255,0.85);" x-text="a"></p>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
{# Événement ICS détecté (extraction calendrier) #}
|
||
<div class="dictia-fade-y rounded mt-1 flex-shrink-0" x-show="aShown >= actions.length"
|
||
style="background:rgba(6,182,212,0.10);border:1px solid rgba(6,182,212,0.30);padding:5px 8px;animation-delay:1800ms;">
|
||
<div class="flex items-center gap-1.5 mb-0.5">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="#06b6d4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:9px;height:9px;" aria-hidden="true">
|
||
<rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>
|
||
</svg>
|
||
<span class="font-mono text-[9px] font-bold tracking-widest" style="color:rgba(6,182,212,0.95);">ÉVÉNEMENT DÉTECTÉ → ICS</span>
|
||
</div>
|
||
<p class="font-mono text-[9px]" style="color:rgba(255,255,255,0.70);">Comité de suivi · 21 janv. 14h00</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 10 : Intégrations animées (logos hub Word, Outlook, Teams, Notion, Obsidian, Zapier...) — Catégorie Distribution #}
|
||
<template x-if="displayMode === 10">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="integModeData()" x-init="init()">
|
||
<div class="flex items-center justify-center gap-2 py-2 flex-shrink-0"
|
||
style="background:rgba(192,38,211,0.06);border-bottom:1px solid rgba(192,38,211,0.12);">
|
||
<span class="dictia-dot-pulse block rounded-full" style="width:5px;height:5px;background:#c026d3;"></span>
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:rgba(192,38,211,0.90);">INTÉGRATIONS HUB</span>
|
||
</div>
|
||
{# Hub central + 8 logos en orbite (grille pour mobile, ring pour desktop) #}
|
||
<div class="flex-1 relative flex items-center justify-center px-3 py-2 overflow-hidden">
|
||
{# Cercle central DictIA #}
|
||
<div class="rounded-full flex items-center justify-center relative z-10"
|
||
style="width:42px;height:42px;background:linear-gradient(135deg,rgba(6,182,212,0.25),rgba(192,38,211,0.25));border:1.5px solid rgba(192,38,211,0.50);box-shadow:0 0 20px rgba(192,38,211,0.30);">
|
||
<span class="font-black text-[10px] grad-text">Dict</span>
|
||
</div>
|
||
{# 8 logos animés en cercle autour du hub (positions absolutes) #}
|
||
<template x-for="(l, li) in LOGOS" :key="l.name">
|
||
<div class="absolute dictia-spring rounded flex items-center justify-center"
|
||
:style="`width:28px;height:28px;left:${l.x}%;top:${l.y}%;transform:translate(-50%,-50%);background:${l.c}18;border:1px solid ${l.c}45;animation-delay:${li * 90 + 200}ms;`">
|
||
<span class="font-mono font-black text-[9px]" :style="`color:${l.c};`" x-text="l.short"></span>
|
||
</div>
|
||
</template>
|
||
{# Lignes de connexion SVG hub→logos #}
|
||
<svg class="absolute inset-0 pointer-events-none" width="100%" height="100%" aria-hidden="true" style="opacity:0.20;">
|
||
<line x1="50%" y1="50%" x2="20%" y2="20%" stroke="#c026d3" stroke-width="0.5" stroke-dasharray="2 2"/>
|
||
<line x1="50%" y1="50%" x2="80%" y2="20%" stroke="#06b6d4" stroke-width="0.5" stroke-dasharray="2 2"/>
|
||
<line x1="50%" y1="50%" x2="20%" y2="80%" stroke="#2563eb" stroke-width="0.5" stroke-dasharray="2 2"/>
|
||
<line x1="50%" y1="50%" x2="80%" y2="80%" stroke="#c026d3" stroke-width="0.5" stroke-dasharray="2 2"/>
|
||
</svg>
|
||
</div>
|
||
<div class="flex items-center justify-center gap-3 px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(192,38,211,0.04);border-top:1px solid rgba(192,38,211,0.10);">
|
||
<span class="font-mono text-[9px] tracking-wide" style="color:rgba(192,38,211,0.75);">REST · Webhook · OAuth2</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 11 : Audit trail (event log horodaté qui a fait quoi) — Catégorie Gouvernance #}
|
||
<template x-if="displayMode === 11">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="auditModeData()" x-init="init()">
|
||
<div class="flex items-center justify-between px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(37,99,235,0.06);border-bottom:1px solid rgba(37,99,235,0.12);">
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:rgba(37,99,235,0.90);">AUDIT TRAIL</span>
|
||
<span class="font-mono text-[9px]" style="color:rgba(37,99,235,0.55);">Loi 25 art. 8</span>
|
||
</div>
|
||
<div class="flex-1 dictia-mode-scroll dictia-fade-bottom flex flex-col gap-1 px-2 py-2">
|
||
<template x-for="(e, ei) in events.slice(0, shown)" :key="ei">
|
||
<div class="rounded flex items-start gap-1.5 dictia-fade-x"
|
||
:style="`background-color:${e.c}0C;border-left:2px solid ${e.c};padding:3px 6px;animation-delay:${ei * 100}ms;`">
|
||
<span class="font-mono text-[9px] font-bold mt-px" :style="`color:${e.c};`" x-text="e.t"></span>
|
||
<span class="rounded font-mono text-[8px] font-bold tracking-widest mt-px"
|
||
style="padding:0 4px;"
|
||
:style="`background:${e.c}22;color:${e.c};`" x-text="e.lvl"></span>
|
||
<p class="font-mono text-[9px] leading-snug flex-1" style="color:rgba(255,255,255,0.78);" x-text="e.msg"></p>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<div class="flex items-center justify-center gap-2 px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(16,185,129,0.06);border-top:1px solid rgba(16,185,129,0.14);">
|
||
<span class="dictia-dot-pulse block rounded-full" style="width:5px;height:5px;background:#10b981;"></span>
|
||
<span class="font-mono text-[9px] font-bold tracking-widest" style="color:rgba(16,185,129,0.90);">CONSENTEMENT TRACÉ · IMMUTABLE</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{# Mode 12 : Conformité Loi 25 + 9 ordres pros (badges défilants + EFVP CAI) — Catégorie Gouvernance #}
|
||
<template x-if="displayMode === 12">
|
||
<div class="dictia-mode-fade w-full h-full flex flex-col overflow-hidden" x-data="loi25ModeData()" x-init="init()">
|
||
<div class="flex items-center justify-center gap-2 py-2 flex-shrink-0"
|
||
style="background:rgba(192,38,211,0.06);border-bottom:1px solid rgba(192,38,211,0.12);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:11px;height:11px;color:rgba(192,38,211,0.90);" aria-hidden="true">
|
||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/>
|
||
</svg>
|
||
<span class="font-mono text-[10px] font-bold tracking-widest" style="color:rgba(192,38,211,0.90);">CONFORMITÉ QUÉBEC</span>
|
||
</div>
|
||
<div class="flex-1 dictia-mode-scroll dictia-fade-bottom flex flex-col gap-1.5 px-3 py-2">
|
||
{# Badges Loi 25 / Loi 96 / EFVP CAI / Cadre IA MCN #}
|
||
<div class="flex flex-wrap gap-1.5">
|
||
<template x-for="(b, bi) in BADGES" :key="b.t">
|
||
<span class="rounded font-mono dictia-spring inline-flex items-center gap-1"
|
||
:style="`background:${b.c}18;border:1px solid ${b.c}45;color:${b.c};padding:2px 6px;animation-delay:${bi * 110}ms;font-size:9px;font-weight:bold;`">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="width:7px;height:7px;" aria-hidden="true">
|
||
<polyline points="20 6 9 17 4 12"/>
|
||
</svg>
|
||
<span x-text="b.t"></span>
|
||
</span>
|
||
</template>
|
||
</div>
|
||
{# 9 ordres pros mappés #}
|
||
<p class="font-mono text-[9px] font-bold uppercase tracking-wider mt-1" style="color:rgba(192,38,211,0.75);">9 ordres professionnels</p>
|
||
<div class="grid grid-cols-3 gap-1">
|
||
<template x-for="(o, oi) in ORDERS" :key="o">
|
||
<span class="rounded font-mono dictia-fade-y text-center text-[9px]"
|
||
style="padding:2px;background:rgba(192,38,211,0.10);border:1px solid rgba(192,38,211,0.25);color:rgba(255,255,255,0.85);"
|
||
:style="`animation-delay:${oi * 60 + 800}ms;`"
|
||
x-text="o"></span>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center justify-center gap-2 px-3 py-2 flex-shrink-0"
|
||
style="background:rgba(16,185,129,0.06);border-top:1px solid rgba(16,185,129,0.14);">
|
||
<span class="font-mono text-[9px] font-bold tracking-widest" style="color:rgba(16,185,129,0.90);">EFVP CAI · CADRE IA MCN</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
</div>
|
||
|
||
{# BOTTOM : 6 boutons feature originaux (Trans / Diari / Lang / Exp / Users / Part) + Auto pill — hauteur fixe 90px #}
|
||
<div class="relative flex flex-col items-center justify-center gap-1.5 z-10 flex-shrink-0"
|
||
style="height: 90px; border-top: 1px solid rgba(255,255,255,0.06); background: linear-gradient(180deg, transparent, rgba(0,0,0,0.32));">
|
||
|
||
{# 6 boutons features originaux (FEATURES indexes 1-6) — visible bottom tab bar #}
|
||
<div class="flex items-end gap-1.5 justify-center">
|
||
<template x-for="i in [1,2,3,4,5,6]" :key="i">
|
||
<button type="button" @click="handleManualSelect(i)"
|
||
class="dictia-feat-btn outline-none cursor-pointer focus-visible:ring-2 focus-visible:ring-white/40 flex flex-col items-center justify-end relative"
|
||
style="width:34px;height:42px;border:none;padding:4px 0 6px;background:transparent;"
|
||
:aria-pressed="selectedFeature === i ? 'true' : 'false'"
|
||
:aria-label="`Fonction : ${FEATURES[i].title}`">
|
||
<span :style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(255,255,255,0.40)'}; transition: color 0.2s, transform 0.15s, filter 0.2s; filter: ${selectedFeature === i ? 'drop-shadow(0 0 6px ' + FEATURES[i].color + 'CC)' : 'none'}; transform: scale(${selectedFeature === i ? 1.15 : 1}); display: inline-block;`">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px;" aria-hidden="true" x-html="featureIconPath(i)"></svg>
|
||
</span>
|
||
<span class="text-[9px] font-mono font-bold mt-0.5 leading-none uppercase tracking-wider"
|
||
:style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(255,255,255,0.55)'};`"
|
||
x-text="featureShortLabel(i)"></span>
|
||
{# Tab indicator bottom border 2px #}
|
||
<span x-show="selectedFeature === i" class="absolute" aria-hidden="true"
|
||
:style="`bottom: 0; left: 4px; right: 4px; height: 2px; background: ${FEATURES[i].color}; border-radius: 1px; box-shadow: 0 0 8px ${FEATURES[i].color};`"></span>
|
||
</button>
|
||
</template>
|
||
</div>
|
||
|
||
{# Auto pill / Manuel countdown — style premium pill #}
|
||
<div class="flex items-center justify-center gap-1.5" style="min-height:14px;">
|
||
<template x-if="!isManual">
|
||
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full"
|
||
style="background:rgba(16,185,129,0.12);border:1px solid rgba(16,185,129,0.30);">
|
||
<span class="dictia-auto-pulse block rounded-full" style="width:5px;height:5px;background:#10b981;"></span>
|
||
<span class="font-mono text-[9px] font-bold tracking-widest uppercase" style="color:#10b981;">Auto</span>
|
||
</span>
|
||
</template>
|
||
<template x-if="isManual">
|
||
<span class="inline-flex flex-col items-center gap-0.5">
|
||
<span class="font-mono text-[9px] text-white/60">Auto reprend bientôt</span>
|
||
<span class="rounded-full overflow-hidden" style="width:56px;height:2px;background:rgba(255,255,255,0.12);">
|
||
<span class="block dictia-countdown-fill h-full" :key="selectedFeature" style="background:linear-gradient(90deg,#06b6d4,#c026d3);"></span>
|
||
</span>
|
||
</span>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>{# /dictia-phone-shell #}
|
||
</div>{# /phone wrapper (glow ring) #}
|
||
|
||
{# Feature info card sous le phone — fond bg-brand-navy SOLIDE (extension visuelle du phone, contraste WCAG AA garanti sur section claire) + accent border-left tab indicator #}
|
||
<div style="min-height:64px;width:280px;max-width:100%;" class="mt-3 mx-auto">
|
||
<div class="dictia-feature-card rounded-xl px-4 py-3 relative bg-brand-navy border border-white/10 overflow-hidden"
|
||
:style="`box-shadow: 0 6px 24px rgba(0,0,0,0.20);`">
|
||
{# Border-left 3px accent — style tab indicator #}
|
||
<span class="absolute left-0 top-0 bottom-0" aria-hidden="true"
|
||
:style="`width:3px;background:${activeColor};box-shadow:0 0 8px ${activeColor}80;`"></span>
|
||
{# Badge top-right #}
|
||
<template x-if="FEATURES[displayMode].badge">
|
||
<span class="absolute text-[10px] px-1.5 py-0.5 rounded font-mono font-bold tracking-wider"
|
||
style="top:8px;right:8px;"
|
||
:style="`background-color: ${activeColor}22; color: ${activeColor}; border: 1px solid ${activeColor}55;`"
|
||
x-text="FEATURES[displayMode].badge"></span>
|
||
</template>
|
||
<div class="flex items-start gap-2.5">
|
||
{# Icon container 32×32 #}
|
||
<span class="rounded flex items-center justify-center flex-shrink-0"
|
||
style="width:32px;height:32px;"
|
||
:style="`background-color: ${activeColor}26; border: 1px solid ${activeColor}40;`"
|
||
aria-hidden="true">
|
||
<span :style="`color: ${activeColor};`">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px;" x-html="featureIconPath(displayMode)"></svg>
|
||
</span>
|
||
</span>
|
||
<div class="flex-1 min-w-0 pr-12">
|
||
<p class="text-sm font-bold text-white leading-tight mb-0.5" x-text="FEATURES[displayMode].title"></p>
|
||
<p class="text-[11px] leading-snug text-white/65" x-text="FEATURES[displayMode].subtitle"></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# Mobile : pills features horizontales scrollables (12 fonctions, indexes 1-12) #}
|
||
<div class="lg:hidden w-full overflow-x-auto dictia-hide-scrollbar" style="scrollbar-width:none;">
|
||
<div class="flex gap-2 px-1 pb-1" style="width:max-content;">
|
||
<template x-for="i in [1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
|
||
<button type="button" @click="handleManualSelect(i)"
|
||
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-full shrink-0 transition-all focus-visible:ring-2 focus-visible:ring-brand-b1"
|
||
:style="`border: 1px solid ${selectedFeature === i ? FEATURES[i].color + '70' : 'rgba(0,0,0,0.10)'}; background-color: ${selectedFeature === i ? FEATURES[i].color + '18' : 'rgba(0,0,0,0.04)'}; outline: none;`"
|
||
:aria-pressed="selectedFeature === i ? 'true' : 'false'"
|
||
:aria-label="`Fonction : ${FEATURES[i].title}`">
|
||
<span :style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(0,0,0,0.40)'}; transition: color 0.2s;`">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:12px;height:12px;" aria-hidden="true" x-html="featureIconPath(i)"></svg>
|
||
</span>
|
||
<span class="text-[11px] font-bold whitespace-nowrap"
|
||
:style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(0,0,0,0.50)'};`"
|
||
x-text="FEATURES[i].title"></span>
|
||
</button>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# ─────────── ZONE RIGHT : IA Mistral premium card + grid 12 features ─────────── #}
|
||
<div class="flex flex-col gap-3 w-full lg:w-[320px] flex-shrink-0 relative z-10">
|
||
|
||
{# Eyebrow + title — alignés sur le système typo #}
|
||
<div class="mb-1 flex items-center justify-between gap-2">
|
||
<div class="flex items-center gap-2">
|
||
<span class="block w-1 h-6 rounded-full" style="background:linear-gradient(180deg,#06b6d4,#c026d3);" aria-hidden="true"></span>
|
||
<div>
|
||
<p class="text-xs font-mono font-bold uppercase tracking-widest text-brand-b3">Fonctions clés</p>
|
||
<p class="font-bold text-lg leading-snug text-brand-navy">Le moteur IA local</p>
|
||
</div>
|
||
</div>
|
||
<span class="text-[10px] font-mono font-bold px-2 py-0.5 rounded-full text-brand-b3 whitespace-nowrap"
|
||
style="background-color:rgba(192,38,211,0.10);border:1px solid rgba(192,38,211,0.25);">12 fonctions</span>
|
||
</div>
|
||
|
||
{# IA Mistral 7B premium card — couleurs brand-b3 (fuchsia officiel) #}
|
||
<div class="relative rounded-xl overflow-hidden border-brand-b3/30 border-[1.5px]"
|
||
style="background-color:rgba(8,12,24,0.92);box-shadow:0 0 28px rgba(192,38,211,0.18), 0 12px 40px -10px rgba(0,0,0,0.5);">
|
||
{# Ambient fuchsia glow #}
|
||
<div class="absolute inset-0 pointer-events-none ia-ambient-glow"
|
||
style="border-radius:inherit;background:radial-gradient(ellipse 120% 60% at 15% 50%, rgba(192,38,211,0.22) 0%, transparent 70%);"
|
||
aria-hidden="true"></div>
|
||
|
||
{# Header — Brain in 40x40 circle (premium feel) #}
|
||
<div class="relative p-5 flex items-start gap-3">
|
||
<div class="relative flex-shrink-0">
|
||
<div class="absolute -inset-3 rounded-full pointer-events-none ia-brain-glow"
|
||
style="background:radial-gradient(circle, rgba(192,38,211,0.55) 0%, transparent 70%);"
|
||
aria-hidden="true"></div>
|
||
<div class="rounded-full flex items-center justify-center relative"
|
||
style="width:40px;height:40px;background:linear-gradient(135deg, rgba(192,38,211,0.30), rgba(192,38,211,0.12));border:1.5px solid rgba(192,38,211,0.50);box-shadow:0 4px 12px rgba(192,38,211,0.20);">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-brand-b3 relative" style="width:20px;height:20px;z-index:1;" aria-hidden="true">
|
||
<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z"/>
|
||
<path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="flex-1 min-w-0">
|
||
<span class="font-bold text-lg text-white block">IA intégrée</span>
|
||
<div class="flex items-center gap-1.5 flex-wrap mt-1.5">
|
||
<span class="text-[10px] px-2 py-0.5 rounded font-mono font-bold tracking-wider text-brand-b3"
|
||
style="background-color:rgba(192,38,211,0.22);border:1px solid rgba(192,38,211,0.45);">MISTRAL 7B</span>
|
||
<span class="text-[10px] px-2 py-0.5 rounded font-mono font-bold ia-local-badge inline-flex items-center gap-1 tracking-widest"
|
||
style="background-color:rgba(16,185,129,0.14);color:#10b981;border:1px solid rgba(16,185,129,0.40);">
|
||
<span class="dictia-dot-pulse block rounded-full" style="width:4px;height:4px;background:#10b981;"></span>
|
||
LOCAL
|
||
</span>
|
||
</div>
|
||
<p class="text-xs mt-2 text-white/65">Résumé · Points d'action · Q&R</p>
|
||
</div>
|
||
</div>
|
||
|
||
{# Divider #}
|
||
<div style="height:1px;background:linear-gradient(90deg, transparent, rgba(192,38,211,0.30), transparent);" aria-hidden="true"></div>
|
||
|
||
{# Performance metrics 3 cells — chiffres en grad-text font-bold #}
|
||
<div class="relative grid grid-cols-3 gap-0">
|
||
<div class="text-center py-3 px-1" style="border-right:1px solid rgba(192,38,211,0.10);">
|
||
<p class="font-mono font-black text-lg leading-none grad-text">0 ms</p>
|
||
<p class="text-[10px] mt-1 uppercase tracking-wider font-semibold" style="color:rgba(192,38,211,0.70);">latence</p>
|
||
</div>
|
||
<div class="text-center py-3 px-1" style="border-right:1px solid rgba(192,38,211,0.10);">
|
||
<p class="font-mono font-black text-lg leading-none" style="color:#10b981;">100 %</p>
|
||
<p class="text-[10px] mt-1 uppercase tracking-wider font-semibold" style="color:rgba(192,38,211,0.70);">privé</p>
|
||
</div>
|
||
<div class="text-center py-3 px-1">
|
||
<p class="font-mono font-black text-lg leading-none grad-text">24/7</p>
|
||
<p class="text-[10px] mt-1 uppercase tracking-wider font-semibold" style="color:rgba(192,38,211,0.70);">dispo</p>
|
||
</div>
|
||
</div>
|
||
|
||
{# Divider #}
|
||
<div style="height:1px;background:linear-gradient(90deg, transparent, rgba(192,38,211,0.22), transparent);" aria-hidden="true"></div>
|
||
|
||
{# Sovereignty bullets — chacun avec icon dans cercle 20×20 brand-b3/[0.15] #}
|
||
<div class="relative p-5 flex flex-col gap-3">
|
||
<div class="flex items-start gap-2.5">
|
||
<span class="rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 bg-brand-b3/[0.15] border border-brand-b3/30"
|
||
style="width:20px;height:20px;"
|
||
aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-brand-b3" style="width:11px;height:11px;">
|
||
<rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/>
|
||
<line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/>
|
||
</svg>
|
||
</span>
|
||
<span class="text-[11px] leading-snug text-white/90"><strong class="font-bold">Données hébergées sur VOS serveurs</strong> · jamais partagées</span>
|
||
</div>
|
||
<div class="flex items-start gap-2.5">
|
||
<span class="rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 bg-brand-b3/[0.15] border border-brand-b3/30"
|
||
style="width:20px;height:20px;"
|
||
aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-brand-b3" style="width:11px;height:11px;">
|
||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/>
|
||
</svg>
|
||
</span>
|
||
<span class="text-[11px] leading-snug text-white/75">Zéro connexion OpenAI · Google · Microsoft</span>
|
||
</div>
|
||
<div class="flex items-start gap-2.5">
|
||
<span class="rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 bg-brand-b3/[0.15] border border-brand-b3/30"
|
||
style="width:20px;height:20px;"
|
||
aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="currentColor" stroke="none" class="text-brand-b3" style="width:11px;height:11px;"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||
</span>
|
||
<span class="text-[11px] leading-snug text-white/75">Inférence hors-ligne · résultats en secondes</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# Feature grid 3×4 — 12 fonctions (FEATURES indexes 1-12, IA mode 0 accessible via Brain card ci-dessus) #}
|
||
<div class="grid grid-cols-3 gap-2">
|
||
<template x-for="i in [1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
|
||
<button type="button" @click="handleManualSelect(i)"
|
||
class="flex flex-col items-center gap-1.5 rounded-xl py-2.5 px-2 outline-none transition-all focus-visible:ring-2 focus-visible:ring-white/40 text-center"
|
||
:style="`border: 1px solid ${selectedFeature === i ? FEATURES[i].color + '70' : 'rgba(255,255,255,0.10)'}; background-color: ${selectedFeature === i ? 'rgba(8,12,24,0.95)' : 'rgba(8,12,24,0.85)'}; box-shadow: ${selectedFeature === i ? '0 0 16px ' + FEATURES[i].color + '40, inset 0 0 16px ' + FEATURES[i].color + '20' : 'none'}; cursor: pointer; min-height:64px;`"
|
||
:aria-pressed="selectedFeature === i ? 'true' : 'false'"
|
||
:aria-label="`Fonction : ${FEATURES[i].title}`">
|
||
<span :style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(255,255,255,0.55)'}; filter: ${selectedFeature === i ? 'drop-shadow(0 0 6px ' + FEATURES[i].color + 'BB)' : 'none'}; transition: all 0.2s; transform: scale(${selectedFeature === i ? 1.15 : 1}); display:inline-block;`">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:18px;height:18px;" aria-hidden="true" x-html="featureIconPath(i)"></svg>
|
||
</span>
|
||
<span class="text-[10px] font-bold leading-tight w-full truncate"
|
||
:style="`color: ${selectedFeature === i ? 'rgba(255,255,255,0.98)' : 'rgba(255,255,255,0.85)'};`"
|
||
x-text="featureGridLabel(i)"></span>
|
||
</button>
|
||
</template>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{# Alpine logic — dictiaDashboard : 12 fonctions visibles (1-12) + IA accessible via Brain card (refonte 2026-04-29) #}
|
||
<script>
|
||
function dictiaDashboard() {
|
||
return {
|
||
/* FEATURES indexes (0-12) — IA mode 0 accessible via Brain card; 1-6 = originaux, 7-12 = nouveaux */
|
||
FEATURES: [
|
||
{ idx: 0, title: 'IA intégrée', subtitle: "Résumé, actions, Q&R", color: '#c026d3', badge: 'Mistral 7B' }, /* brand-b3 */
|
||
{ idx: 1, title: 'Transcription', subtitle: 'Parole → texte en temps réel', color: '#06b6d4', badge: 'Whisper AI' }, /* brand-b2 */
|
||
{ idx: 2, title: 'Diarisation', subtitle: 'Identification des locuteurs', color: '#2563eb', badge: null }, /* brand-b1 */
|
||
{ idx: 3, title: '99+ langues', subtitle: 'Détection automatique', color: '#06b6d4', badge: null }, /* brand-b2 */
|
||
{ idx: 4, title: 'Exports', subtitle: 'DOCX, SRT, JSON, PDF', color: '#2563eb', badge: null }, /* brand-b1 */
|
||
{ idx: 5, title: 'Utilisateurs illimités', subtitle: 'Toute votre équipe', color: '#c026d3', badge: 'Illimité' }, /* brand-b3 */
|
||
{ idx: 6, title: 'Partage & Classement', subtitle: 'Dossiers, tags, recherche', color: '#06b6d4', badge: null }, /* brand-b2 */
|
||
/* === Nouveaux modes 2026-04-29 === */
|
||
{ idx: 7, title: 'Enregistrement live', subtitle: 'Mic + système + minuteur', color: '#06b6d4', badge: 'PWA' }, /* brand-b2 */
|
||
{ idx: 8, title: 'Recherche IA', subtitle: 'Sémantique sur toute la bibliothèque', color: '#06b6d4', badge: 'RAG' }, /* brand-b2 */
|
||
{ idx: 9, title: 'Résumé + actions', subtitle: 'Décisions, ICS, événements', color: '#2563eb', badge: 'Nemo 12B' }, /* brand-b1 */
|
||
{ idx: 10, title: 'Intégrations', subtitle: 'Word, Notion, Teams, Zapier...', color: '#c026d3', badge: '8 hubs' }, /* brand-b3 */
|
||
{ idx: 11, title: 'Audit trail', subtitle: 'Traçabilité Loi 25 immutable', color: '#2563eb', badge: 'Loi 25' }, /* brand-b1 */
|
||
{ idx: 12, title: 'Conformité', subtitle: '9 ordres pros · EFVP CAI', color: '#c026d3', badge: 'MCN' } /* brand-b3 */
|
||
],
|
||
selectedFeature: 1,
|
||
isManual: false,
|
||
autoCycleTimer: null,
|
||
manualResetTimer: null,
|
||
get displayMode() { return this.selectedFeature; },
|
||
get activeColor() { return this.FEATURES[this.displayMode].color; },
|
||
init() {
|
||
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||
this.startAutoCycle();
|
||
},
|
||
/* Auto-cycle logic : avance 1→12 (skip 0=IA mode, accessible via Brain card), 1100ms each.
|
||
Cycle complet : 12 features × 1100ms ≈ 13.2s puis recommence à 1. */
|
||
startAutoCycle() {
|
||
if (this.autoCycleTimer) clearInterval(this.autoCycleTimer);
|
||
this.autoCycleTimer = setInterval(() => {
|
||
/* Avance 1→2→...→12, puis retour à 1 (skip 0=IA) */
|
||
this.selectedFeature = this.selectedFeature >= 12 ? 1 : this.selectedFeature + 1;
|
||
if (this.selectedFeature === 0) this.selectedFeature = 1;
|
||
}, 1100);
|
||
},
|
||
/* Click manuel sur un bouton feature : sélectionne ce mode + freeze auto 4500ms */
|
||
handleManualSelect(i) {
|
||
this.selectedFeature = i;
|
||
this.isManual = true;
|
||
if (this.autoCycleTimer) { clearInterval(this.autoCycleTimer); this.autoCycleTimer = null; }
|
||
if (this.manualResetTimer) clearTimeout(this.manualResetTimer);
|
||
this.manualResetTimer = setTimeout(() => {
|
||
this.isManual = false;
|
||
this.startAutoCycle();
|
||
}, 4500);
|
||
},
|
||
featureIconPath(i) {
|
||
const paths = {
|
||
0: '<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z"/><path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z"/>',
|
||
1: '<rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/>',
|
||
2: '<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"/>',
|
||
3: '<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"/>',
|
||
4: '<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="12" y1="18" x2="12" y2="12"/><polyline points="9 15 12 18 15 15"/>',
|
||
5: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/>',
|
||
6: '<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',
|
||
7: '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/>',
|
||
8: '<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>',
|
||
9: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/>',
|
||
10: '<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/>',
|
||
11: '<path d="M12 2v6"/><path d="M12 22v-6"/><path d="M4.93 4.93l4.24 4.24"/><path d="M14.83 14.83l4.24 4.24"/><circle cx="12" cy="12" r="4"/>',
|
||
12: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/>'
|
||
};
|
||
return paths[i] || '';
|
||
},
|
||
featureShortLabel(i) {
|
||
const labels = { 1: 'Trans', 2: 'Diari', 3: 'Lang', 4: 'Exp', 5: 'Users', 6: 'Part',
|
||
7: 'Rec', 8: 'Search', 9: 'Résu', 10: 'Hub', 11: 'Audit', 12: 'Conf' };
|
||
return labels[i] || '';
|
||
},
|
||
/* Labels compacts pour la grid 3×4 du right panel (raccourcis pour ne pas tronquer) */
|
||
featureGridLabel(i) {
|
||
const labels = {
|
||
1: 'Transcription', 2: 'Diarisation', 3: '99+ langues', 4: 'Exports',
|
||
5: 'Utilisateurs', 6: 'Partage', 7: 'Recording', 8: 'Recherche IA',
|
||
9: 'Résumés', 10: 'Intégrations', 11: 'Audit trail', 12: 'Conformité'
|
||
};
|
||
return labels[i] || '';
|
||
}
|
||
};
|
||
}
|
||
|
||
/* Mode 1 — Transcription : upload bar (60ms × 20 steps) puis words (155ms each) */
|
||
function trModeData() {
|
||
return {
|
||
phase: 'upload', progress: 0, n: 0,
|
||
words: "La réunion du lundi 12 janvier débutera à 9h. Ordre du jour : budget Q4 et les objectifs du trimestre. Points d'action à définir en fin de séance.".split(' '),
|
||
_timers: [],
|
||
init() {
|
||
this._cleanup();
|
||
this.phase = 'upload'; this.progress = 0; this.n = 0;
|
||
const iv1 = setInterval(() => {
|
||
this.progress = Math.min(this.progress + 5, 100);
|
||
if (this.progress >= 100) {
|
||
clearInterval(iv1);
|
||
const t1 = setTimeout(() => {
|
||
this.phase = 'transcribing'; this.n = 0;
|
||
const iv2 = setInterval(() => {
|
||
if (this.n >= this.words.length - 1) {
|
||
clearInterval(iv2);
|
||
const t2 = setTimeout(() => this.init(), 900);
|
||
this._timers.push(t2);
|
||
} else this.n++;
|
||
}, 155);
|
||
this._timers.push(iv2);
|
||
}, 450);
|
||
this._timers.push(t1);
|
||
}
|
||
}, 60);
|
||
this._timers.push(iv1);
|
||
},
|
||
_cleanup() { this._timers.forEach(t => { clearInterval(t); clearTimeout(t); }); this._timers = []; }
|
||
};
|
||
}
|
||
|
||
/* Mode 2 — Diarisation : 6 messages staggered 1600ms */
|
||
function diaModeData() {
|
||
return {
|
||
CONVO: [
|
||
{ s: "Sophie", c: "#06b6d4", t: "La réunion commence à 9h." }, /* brand-b2 cyan */
|
||
{ s: "Marc", c: "#2563eb", t: "J'ai les chiffres du Q4 ici." }, /* brand-b1 blue */
|
||
{ s: "Julie", c: "#c026d3", t: "Je propose reporter la démo." }, /* brand-b3 fuchsia */
|
||
{ s: "Sophie", c: "#06b6d4", t: "Accord — on vote là-dessus ?" },
|
||
{ s: "Marc", c: "#2563eb", t: "Approuvé à l'unanimité." },
|
||
{ s: "Julie", c: "#c026d3", t: "Action : CR envoyé ce soir." }
|
||
],
|
||
shownCount: 1, cycle: 0, _iv: null,
|
||
get startIdx() { return Math.max(0, this.shownCount - 4); },
|
||
get visible() { return this.CONVO.slice(this.startIdx, this.shownCount); },
|
||
init() {
|
||
if (this._iv) clearInterval(this._iv);
|
||
this.shownCount = 1;
|
||
this._iv = setInterval(() => {
|
||
if (this.shownCount >= this.CONVO.length) {
|
||
clearInterval(this._iv);
|
||
setTimeout(() => { this.cycle++; this.init(); }, 800);
|
||
} else this.shownCount++;
|
||
}, 1600);
|
||
}
|
||
};
|
||
}
|
||
|
||
/* Mode 3 — Langues : grille 100 codes (ripple depuis centre + highlight aléatoire) */
|
||
function langModeData() {
|
||
return {
|
||
/* Cycle EXCLUSIVEMENT entre brand-b1 / brand-b2 / brand-b3 (palette canonique stricte) */
|
||
LANG_COLORS: ['#2563eb','#06b6d4','#c026d3','#06b6d4','#2563eb','#c026d3','#2563eb','#06b6d4'],
|
||
LANGS: [
|
||
'FR','EN','ES','DE','PT','IT','NL','PL','ZH','JA',
|
||
'KO','AR','RU','HI','TR','VI','TH','SV','DA','NO',
|
||
'FI','CS','RO','HU','EL','HE','UK','BG','FA','ID',
|
||
'MS','NB','BN','UR','SW','TL','SK','HR','SR','SL',
|
||
'LT','LV','ET','IS','MT','GL','CA','EU','CY','GA',
|
||
'AF','MK','BE','LB','MR','TA','TE','KN','ML','PA',
|
||
'GU','SI','MY','KM','LO','KA','AZ','KK','UZ','TK',
|
||
'MN','NE','PS','AM','SQ','HY','MG','YO','SO','ZU',
|
||
'NY','XH','IG','HA','SN','ST','CEB','HT','JW','KY',
|
||
'TG','BS','OC','BR','FO','LG','YI','TT','SD','WA'
|
||
],
|
||
/* HIGHLIGHTS : 5 indexes aléatoires qui pulsent (regenerated chaque cycle) */
|
||
HIGHLIGHTS: (() => {
|
||
const set = new Set();
|
||
while (set.size < 5) set.add(Math.floor(Math.random() * 100));
|
||
return Array.from(set);
|
||
})(),
|
||
/* rippleDelay : delay calculé selon distance au centre (50, ~ idx 49) */
|
||
rippleDelay(i) {
|
||
const cols = 7, row = Math.floor(i / cols), col = i % cols;
|
||
const centerRow = 7, centerCol = 3;
|
||
const dist = Math.sqrt(Math.pow(row - centerRow, 2) + Math.pow(col - centerCol, 2));
|
||
return Math.round(dist * 28);
|
||
}
|
||
};
|
||
}
|
||
|
||
/* Mode 4 — Exports : 7 file types staggered 90ms (CSS-only) — palette brand stricte */
|
||
function expModeData() {
|
||
return {
|
||
FILE_TYPES: [
|
||
{ ext: 'DOCX', bg: '#2563eb', fg: '#ffffff', sym: 'W' }, /* brand-b1 blue */
|
||
{ ext: 'PDF', bg: '#dc2626', fg: '#ffffff', sym: 'PDF' }, /* red sémantique conservé pour fichier PDF */
|
||
{ ext: 'SRT', bg: '#c026d3', fg: '#ffffff', sym: 'CC' }, /* brand-b3 fuchsia */
|
||
{ ext: 'VTT', bg: '#c026d3', fg: '#ffffff', sym: 'CC' }, /* brand-b3 fuchsia */
|
||
{ ext: 'TXT', bg: '#374151', fg: '#d1d5db', sym: '≡' }, /* gray-700 neutre (sémantique TXT raw) */
|
||
{ ext: 'JSON', bg: '#06b6d4', fg: '#ffffff', sym: '{}' }, /* brand-b2 aqua */
|
||
{ ext: 'MD', bg: '#2563eb', fg: '#ffffff', sym: '#' } /* brand-b1 blue */
|
||
]
|
||
};
|
||
}
|
||
|
||
/* Mode 5 — Users : multiplication 1→20 (200ms each, loop after 700ms) avec couleurs variées */
|
||
function usersModeData() {
|
||
return {
|
||
MAX: 20, count: 1, cycle: 0, _iv: null,
|
||
/* EXCLUSIVEMENT brand-b1 / brand-b2 / brand-b3 (palette canonique stricte) */
|
||
USER_COLORS: ['#c026d3', '#06b6d4', '#2563eb'],
|
||
init() {
|
||
if (this._iv) clearInterval(this._iv);
|
||
this.count = 1;
|
||
this._iv = setInterval(() => {
|
||
if (this.count >= this.MAX) {
|
||
clearInterval(this._iv);
|
||
setTimeout(() => { this.cycle++; this.init(); }, 700);
|
||
} else this.count++;
|
||
}, 200);
|
||
}
|
||
};
|
||
}
|
||
|
||
/* Mode 0 — IA chat : Q&R typed char-by-char (28ms/char) */
|
||
function iaModeData() {
|
||
return {
|
||
msgs: [], cycle: 0, _timers: [],
|
||
CHAT: [
|
||
{ role: 'user', text: "Quels sont les points d'action ?" },
|
||
{ role: 'bot', text: "→ Envoyer CR avant 17h\n→ Contacter client vendredi\n→ Réviser budget avec Luc" },
|
||
{ role: 'user', text: "Qui a ouvert la réunion ?" },
|
||
{ role: 'bot', text: "Sophie a ouvert la séance à 9h00." }
|
||
],
|
||
init() {
|
||
this._cleanup();
|
||
this.msgs = [];
|
||
const t0 = setTimeout(() => { this.msgs = [{ role: 'user', text: this.CHAT[0].text }]; }, 300);
|
||
this._timers.push(t0);
|
||
const t1 = setTimeout(() => {
|
||
const bot1 = this.CHAT[1].text; let i = 0;
|
||
const iv = setInterval(() => {
|
||
i++;
|
||
this.msgs = [
|
||
{ role: 'user', text: this.CHAT[0].text },
|
||
{ role: 'bot', text: bot1.slice(0, i) }
|
||
];
|
||
if (i >= bot1.length) {
|
||
clearInterval(iv);
|
||
const t2 = setTimeout(() => {
|
||
this.msgs = [
|
||
{ role: 'user', text: this.CHAT[0].text },
|
||
{ role: 'bot', text: this.CHAT[1].text },
|
||
{ role: 'user', text: this.CHAT[2].text }
|
||
];
|
||
const t3 = setTimeout(() => {
|
||
const bot2 = this.CHAT[3].text; let j = 0;
|
||
const iv2 = setInterval(() => {
|
||
j++;
|
||
this.msgs = [
|
||
{ role: 'user', text: this.CHAT[0].text },
|
||
{ role: 'bot', text: this.CHAT[1].text },
|
||
{ role: 'user', text: this.CHAT[2].text },
|
||
{ role: 'bot', text: bot2.slice(0, j) }
|
||
];
|
||
if (j >= bot2.length) {
|
||
clearInterval(iv2);
|
||
const t4 = setTimeout(() => { this.cycle++; this.init(); }, 1200);
|
||
this._timers.push(t4);
|
||
}
|
||
}, 40);
|
||
this._timers.push(iv2);
|
||
}, 500);
|
||
this._timers.push(t3);
|
||
}, 700);
|
||
this._timers.push(t2);
|
||
}
|
||
}, 28);
|
||
this._timers.push(iv);
|
||
}, 900);
|
||
this._timers.push(t1);
|
||
},
|
||
_cleanup() { this._timers.forEach(t => { clearInterval(t); clearTimeout(t); }); this._timers = []; }
|
||
};
|
||
}
|
||
|
||
/* === MODES AJOUTÉS 2026-04-29 === */
|
||
|
||
/* Mode 7 — Recording live : minuteur incrémentiel + waveform random pattern */
|
||
function recModeData() {
|
||
return {
|
||
sec: 0, BARS: [], _iv: null, _ivBars: null,
|
||
get time() {
|
||
const m = String(Math.floor(this.sec / 60)).padStart(2, '0');
|
||
const s = String(this.sec % 60).padStart(2, '0');
|
||
return `${m}:${s}`;
|
||
},
|
||
get size() { return Math.round(this.sec * 0.16 * 10) / 10; },
|
||
init() {
|
||
if (this._iv) clearInterval(this._iv);
|
||
if (this._ivBars) clearInterval(this._ivBars);
|
||
this.sec = 0;
|
||
/* 24 bars hauteur initiale random 6-26px */
|
||
this.BARS = Array.from({ length: 24 }, () => Math.floor(Math.random() * 20) + 6);
|
||
this._iv = setInterval(() => {
|
||
this.sec++;
|
||
if (this.sec >= 99) { clearInterval(this._iv); setTimeout(() => this.init(), 800); }
|
||
}, 1000);
|
||
/* Update bars heights chaque 250ms pour effet "live" */
|
||
this._ivBars = setInterval(() => {
|
||
this.BARS = this.BARS.map(() => Math.floor(Math.random() * 20) + 6);
|
||
}, 250);
|
||
}
|
||
};
|
||
}
|
||
|
||
/* Mode 8 — Recherche sémantique : query typed + résultats matched */
|
||
function searchModeData() {
|
||
return {
|
||
QUERY: 'préoccupations budgétaires',
|
||
query: '', results: [], _timers: [],
|
||
RESULTS_DATA: [
|
||
{ title: 'CR-Réunion-Jan14', match: 'contraintes budgétaires Q4', score: 94, c: '#06b6d4' },
|
||
{ title: 'Comité-Direction', match: 'compressions à anticiper', score: 87, c: '#2563eb' },
|
||
{ title: 'Entretien-Sophie', match: 'inquiétudes financières', score: 82, c: '#c026d3' }
|
||
],
|
||
init() {
|
||
this._cleanup();
|
||
this.query = ''; this.results = [];
|
||
let i = 0;
|
||
const ivType = setInterval(() => {
|
||
i++; this.query = this.QUERY.slice(0, i);
|
||
if (i >= this.QUERY.length) {
|
||
clearInterval(ivType);
|
||
const t1 = setTimeout(() => { this.results = this.RESULTS_DATA; }, 500);
|
||
this._timers.push(t1);
|
||
const t2 = setTimeout(() => this.init(), 5500);
|
||
this._timers.push(t2);
|
||
}
|
||
}, 60);
|
||
this._timers.push(ivType);
|
||
},
|
||
_cleanup() { this._timers.forEach(t => { clearInterval(t); clearTimeout(t); }); this._timers = []; }
|
||
};
|
||
}
|
||
|
||
/* Mode 9 — Résumé + extraction d'événements : décisions/actions stagger */
|
||
function summaryModeData() {
|
||
return {
|
||
decisions: [
|
||
'Approuvé budget Q4 à l\'unanimité',
|
||
'Reporter démo à février',
|
||
'Validation finale 25 janv.'
|
||
],
|
||
actions: [
|
||
'Marc : envoyer CR avant 17h',
|
||
'Sophie : contacter client vendredi',
|
||
'Julie : réviser budget avec Luc'
|
||
],
|
||
dShown: 0, aShown: 0, _timers: [],
|
||
init() {
|
||
this._cleanup();
|
||
this.dShown = 0; this.aShown = 0;
|
||
let di = 0;
|
||
const ivD = setInterval(() => {
|
||
di++; this.dShown = di;
|
||
if (di >= this.decisions.length) {
|
||
clearInterval(ivD);
|
||
let ai = 0;
|
||
const t1 = setTimeout(() => {
|
||
const ivA = setInterval(() => {
|
||
ai++; this.aShown = ai;
|
||
if (ai >= this.actions.length) {
|
||
clearInterval(ivA);
|
||
const t2 = setTimeout(() => this.init(), 3500);
|
||
this._timers.push(t2);
|
||
}
|
||
}, 600);
|
||
this._timers.push(ivA);
|
||
}, 700);
|
||
this._timers.push(t1);
|
||
}
|
||
}, 600);
|
||
this._timers.push(ivD);
|
||
},
|
||
_cleanup() { this._timers.forEach(t => { clearInterval(t); clearTimeout(t); }); this._timers = []; }
|
||
};
|
||
}
|
||
|
||
/* Mode 10 — Intégrations Hub : 8 logos en orbite */
|
||
function integModeData() {
|
||
return {
|
||
LOGOS: [
|
||
{ name: 'Word', short: 'W', c: '#2563eb', x: 20, y: 30 },
|
||
{ name: 'Outlook', short: 'O', c: '#06b6d4', x: 80, y: 30 },
|
||
{ name: 'Teams', short: 'T', c: '#c026d3', x: 15, y: 55 },
|
||
{ name: 'Notion', short: 'N', c: '#2563eb', x: 85, y: 55 },
|
||
{ name: 'Obsidian', short: 'Ob', c: '#c026d3', x: 25, y: 80 },
|
||
{ name: 'Zapier', short: 'Z', c: '#06b6d4', x: 75, y: 80 },
|
||
{ name: 'Make', short: 'Mk', c: '#2563eb', x: 50, y: 18 },
|
||
{ name: 'n8n', short: 'n8', c: '#06b6d4', x: 50, y: 88 }
|
||
],
|
||
init() {}
|
||
};
|
||
}
|
||
|
||
/* Mode 11 — Audit trail : event log staggered */
|
||
function auditModeData() {
|
||
return {
|
||
events: [
|
||
{ t: '09:01', lvl: 'INFO', c: '#06b6d4', msg: 'Sophie a uploadé reunion-jan14.mp3' },
|
||
{ t: '09:02', lvl: 'AUTH', c: '#2563eb', msg: 'Consentement explicite enregistré' },
|
||
{ t: '09:04', lvl: 'PROC', c: '#c026d3', msg: 'WhisperX terminé · diarisation 3 loc.' },
|
||
{ t: '09:08', lvl: 'READ', c: '#06b6d4', msg: 'Marc a écouté le segment 02:14-04:30' },
|
||
{ t: '09:12', lvl: 'EXP', c: '#2563eb', msg: 'Export DOCX par Julie · IP 198.x.x.42' },
|
||
{ t: '09:15', lvl: 'SHARE', c: '#c026d3', msg: 'Partage interne accordé : Luc (édition)' }
|
||
],
|
||
shown: 0, _iv: null,
|
||
init() {
|
||
if (this._iv) clearInterval(this._iv);
|
||
this.shown = 0;
|
||
this._iv = setInterval(() => {
|
||
if (this.shown >= this.events.length) {
|
||
clearInterval(this._iv);
|
||
setTimeout(() => this.init(), 2400);
|
||
} else this.shown++;
|
||
}, 700);
|
||
}
|
||
};
|
||
}
|
||
|
||
/* Mode 12 — Conformité Loi 25 + 9 ordres pros */
|
||
function loi25ModeData() {
|
||
return {
|
||
BADGES: [
|
||
{ t: 'Loi 25', c: '#06b6d4' },
|
||
{ t: 'Loi 96', c: '#2563eb' },
|
||
{ t: 'EFVP CAI', c: '#c026d3' },
|
||
{ t: 'Cadre IA MCN', c: '#06b6d4' },
|
||
{ t: 'AGPL v3', c: '#2563eb' },
|
||
{ t: '0 Cloud Act US', c: '#c026d3' }
|
||
],
|
||
ORDERS: ['Barreau', 'CNQ', 'CPA', 'ChAD', 'OACIQ', 'CMQ', 'OIIQ', 'OPQ', 'OEQ'],
|
||
init() {}
|
||
};
|
||
}
|
||
</script>
|
||
</section>
|
||
|
||
{# ===== INTÉGRATIONS ===== #}
|
||
<section id="integrations" class="bg-brand-bg py-20 scroll-mt-32" aria-labelledby="integrations-title">
|
||
<div class="max-w-[1200px] mx-auto px-6">
|
||
<div class="text-center max-w-3xl mx-auto mb-14">
|
||
<p class="eyebrow grad-text mb-4">INTÉGRATIONS</p>
|
||
<h2 id="integrations-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black text-brand-navy">
|
||
<span class="ani-underline" data-ani-underline>Branchez DictIA à votre stack existant.</span>
|
||
</h2>
|
||
<p class="text-lg text-brand-navy/70 mt-6">
|
||
Webhooks REST, plugin Word natif et connecteurs Zapier / Make / n8n auto-hébergés. API documentée — vous gardez le contrôle des flux.
|
||
</p>
|
||
</div>
|
||
|
||
{%- set icon_chat_box = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>' -%}
|
||
{%- set icon_book = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" aria-hidden="true"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V3H6.5A2.5 2.5 0 0 0 4 5.5z"/><path d="M4 19.5V21h16v-4"/></svg>' -%}
|
||
{%- set icon_gear = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.9.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.9 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.9l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.9.3h.1a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.9-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.9v.1a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>' -%}
|
||
|
||
{%- set integ_groups = [
|
||
{
|
||
'title': 'Communication',
|
||
'desc': 'Importez vos enregistrements depuis vos outils de réunion et de pratique.',
|
||
'icon': icon_chat_box,
|
||
'tools': [
|
||
{'name': 'Microsoft Teams', 'detail': 'Export depuis enregistrements'},
|
||
{'name': 'Microsoft Outlook','detail': 'Pièces jointes audio'},
|
||
{'name': 'Clio Manage', 'detail': 'Pour avocats'},
|
||
{'name': 'PCLaw', 'detail': 'Pour avocats'}
|
||
]
|
||
},
|
||
{
|
||
'title': 'Knowledge / notes',
|
||
'desc': 'Synchronisez transcriptions et résumés vers vos bases de connaissances.',
|
||
'icon': icon_book,
|
||
'tools': [
|
||
{'name': 'Obsidian', 'detail': 'via Markdown'},
|
||
{'name': 'Notion', 'detail': 'via Markdown'},
|
||
{'name': 'Logseq', 'detail': 'via Markdown'},
|
||
{'name': 'Word', 'detail': 'via DOCX'}
|
||
]
|
||
},
|
||
{
|
||
'title': 'Automatisation',
|
||
'desc': 'Déclenchez vos workflows à chaque transcription terminée.',
|
||
'icon': icon_gear,
|
||
'tools': [
|
||
{'name': 'Zapier', 'detail': 'No-code'},
|
||
{'name': 'Make (Integromat)','detail': 'Scénarios visuels'},
|
||
{'name': 'n8n', 'detail': 'Open source self-host'}
|
||
]
|
||
}
|
||
] -%}
|
||
|
||
<div class="grid md:grid-cols-3 gap-6">
|
||
{% for group in integ_groups %}
|
||
<div class="ani-fade bg-white p-6 rounded border border-brand-border" style="--delay: {{ loop.index0 * 80 }}ms;" data-ani-fade>
|
||
<div class="flex items-center gap-2 mb-2 text-brand-b1" aria-hidden="true">
|
||
{{ group.icon | safe }}
|
||
<span class="text-xs uppercase tracking-wider font-bold text-brand-navy">{{ group.title }}</span>
|
||
</div>
|
||
<p class="text-sm text-brand-navy/70 mb-5">{{ group.desc }}</p>
|
||
<ul class="grid grid-cols-2 gap-2" role="list">
|
||
{% for t in group.tools %}
|
||
<li class="ani-lift bg-brand-bg border border-brand-border p-3 rounded">
|
||
<p class="text-sm font-semibold text-brand-navy font-mono leading-tight">{{ t.name }}</p>
|
||
<p class="text-[11px] text-brand-navy/60 mt-0.5">{{ t.detail }}</p>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
<p class="text-xs text-brand-navy/70 text-center mt-8 max-w-2xl mx-auto">
|
||
Microsoft, Notion, Obsidian, Logseq, Clio, PCLaw, Zapier, Make et n8n sont des marques de leurs propriétaires respectifs. DictIA n'est pas affilié à ces produits.
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
{# ===== ARCHITECTURE & INFRASTRUCTURE ===== #}
|
||
<section id="architecture" class="relative bg-brand-navy text-white py-20 scroll-mt-32 overflow-hidden" aria-labelledby="architecture-title">
|
||
<div class="absolute top-1/2 right-1/4 w-[500px] h-[500px] rounded-full pointer-events-none" aria-hidden="true"
|
||
style="background: radial-gradient(circle, rgba(6,182,212,0.08) 0%, transparent 60%); filter: blur(60px);"></div>
|
||
|
||
<div class="relative max-w-[1200px] mx-auto px-6">
|
||
<div class="text-center max-w-3xl mx-auto mb-14">
|
||
<p class="eyebrow grad-text mb-4">ARCHITECTURE & INFRASTRUCTURE</p>
|
||
<h2 id="architecture-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black">
|
||
<span class="ani-underline" data-ani-underline>Quatre formules selon votre organisation.</span>
|
||
</h2>
|
||
<p class="text-lg text-white/70 mt-6">
|
||
Cloud BASIC, ESSENTIEL et PRO tournent sur GPU NVIDIA L4 à OVH Beauharnois (Québec). DictIA LOCAL fonctionne 100 % hors-ligne chez vous. Toutes les formules incluent aucune limite utilisateurs.
|
||
</p>
|
||
</div>
|
||
|
||
{%- set tiers = [
|
||
{
|
||
'name': 'Cloud BASIC',
|
||
'tagline': 'Solopreneur · petite équipe',
|
||
'gpu': 'NVIDIA L4 partagé',
|
||
'users': 'Aucune limite',
|
||
'setup': '—',
|
||
'monthly': '189 $',
|
||
'host': 'OVH Beauharnois (QC)',
|
||
'llm': 'Mistral Nemo 12B',
|
||
'recommended': False,
|
||
'features': ['Transcription WhisperX Large-v3', 'Diarisation pyannote', '~165 h audio/mois · 100 Go', 'Exports DOCX, PDF, SRT, VTT, TXT, JSON, MD', 'Self-service · 0 $ d’installation']
|
||
},
|
||
{
|
||
'name': 'Cloud ESSENTIEL',
|
||
'tagline': 'Cabinet en croissance',
|
||
'gpu': 'L4 partagé étendu',
|
||
'users': 'Aucune limite',
|
||
'setup': '—',
|
||
'monthly': '349 $',
|
||
'host': 'OVH Beauharnois (QC)',
|
||
'llm': 'Mistral Nemo 12B',
|
||
'recommended': False,
|
||
'features': ['Tout Cloud BASIC', '~330 h audio/mois · 200 Go', 'Onboarding assisté', 'Templates métier (avocat · notaire · CPA · médecin)']
|
||
},
|
||
{
|
||
'name': 'Cloud PRO',
|
||
'tagline': 'Usage intensif multi-postes',
|
||
'gpu': 'NVIDIA L4 dédié priorité',
|
||
'users': 'Aucune limite',
|
||
'setup': '485 $',
|
||
'monthly': '549 $',
|
||
'host': 'OVH Beauharnois (QC)',
|
||
'llm': 'Mistral Nemo 12B',
|
||
'recommended': True,
|
||
'features': ['Tout Cloud ESSENTIEL', '~660 h audio/mois · 500 Go', 'GPU dédié priorité (latence garantie)', 'Onboarding assisté inclus (485 $ unique)', 'Multi-sites et télétravail']
|
||
},
|
||
{
|
||
'name': 'DictIA LOCAL',
|
||
'tagline': '100 % hors-ligne · chez vous',
|
||
'gpu': 'RTX 5070 Ti 16 Go',
|
||
'users': 'Aucune limite',
|
||
'setup': '5 998 $',
|
||
'monthly': '500 $/an dès An 2',
|
||
'host': 'Chez le client',
|
||
'llm': 'Mistral 7B local',
|
||
'recommended': False,
|
||
'features': ['Tout Cloud PRO en mode local', '~1 100 h audio/mois · 2 To SSD', 'GPU local dédié', 'Données jamais sortantes', 'Admissible achat direct gouv. (≤ 34 700 $)']
|
||
}
|
||
] -%}
|
||
|
||
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6 items-stretch">
|
||
{% for tier in tiers %}
|
||
<article class="ani-fade {% if tier.recommended %}card-pulse-glow grad-bg p-[1.5px] rounded{% endif %} relative h-full"
|
||
style="--delay: {{ loop.index0 * 100 }}ms;"
|
||
data-ani-fade>
|
||
{% if tier.recommended %}
|
||
<span class="absolute -top-3 left-1/2 -translate-x-1/2 grad-bg text-white text-xs font-bold px-3 py-1 rounded-full inline-flex items-center gap-1.5 z-10">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-3 h-3" aria-hidden="true"><path d="M12 2l2.9 6.9L22 10l-5.5 4.8L18 22l-6-3.6L6 22l1.5-7.2L2 10l7.1-1.1z"/></svg>
|
||
RECOMMANDÉ
|
||
</span>
|
||
{% endif %}
|
||
<div class="bg-brand-navy2 p-6 rounded border border-white/[0.08] h-full flex flex-col">
|
||
<header class="mb-4 pb-4 border-b border-white/[0.08]">
|
||
<h3 class="text-xl font-black mb-1 text-white">{{ tier.name | safe }}</h3>
|
||
<p class="text-xs uppercase tracking-wider text-white/60">{{ tier.tagline | safe }}</p>
|
||
</header>
|
||
<dl class="grid grid-cols-2 gap-x-4 gap-y-2 mb-5 text-xs">
|
||
<dt class="text-white/60">GPU</dt><dd class="font-mono text-white">{{ tier.gpu | safe }}</dd>
|
||
<dt class="text-white/60">Utilisateurs</dt><dd class="text-white">{{ tier.users | safe }}</dd>
|
||
<dt class="text-white/60">Hébergement</dt><dd class="text-white">{{ tier.host | safe }}</dd>
|
||
<dt class="text-white/60">LLM résumés</dt><dd class="text-white">{{ tier.llm | safe }}</dd>
|
||
</dl>
|
||
<div class="mb-5 pb-5 border-b border-white/[0.08]">
|
||
{% if tier.setup != '—' %}
|
||
<div class="text-3xl font-black grad-text leading-none">{{ tier.setup | safe }}</div>
|
||
<div class="text-xs text-white/60 mt-1">{% if tier.name == 'DictIA LOCAL' %}An 1 · puis {{ tier.monthly | safe }}{% else %}setup unique + {{ tier.monthly | safe }} / mois{% endif %}</div>
|
||
{% else %}
|
||
<div class="text-3xl font-black grad-text leading-none">{{ tier.monthly | safe }}</div>
|
||
<div class="text-xs text-white/60 mt-1">par mois · sans frais d’installation</div>
|
||
{% endif %}
|
||
</div>
|
||
<ul class="space-y-2 mb-6 flex-grow text-sm" role="list">
|
||
{% for feat in tier.features %}
|
||
<li class="flex items-start gap-2 text-white/80">
|
||
{{ icon_check | safe }}
|
||
<span>{{ feat | safe }}</span>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
{% from 'macros/button.html' import button %}
|
||
{{ button('Voir le détail tarifs', href='/tarifs', variant='primary' if tier.recommended else 'ghost', size='md') }}
|
||
</div>
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
{# Bloc inclus dans le forfait DictIA LOCAL #}
|
||
<div class="mt-12 max-w-4xl mx-auto bg-white/[0.05] backdrop-blur-sm p-6 rounded border border-white/[0.08]">
|
||
<p class="eyebrow grad-text mb-3">INCLUS DANS LE FORFAIT DICTIA LOCAL (5 998 $ AN 1)</p>
|
||
<ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3 text-sm" role="list">
|
||
{% for inc in [
|
||
'PC + GPU RTX 5070 Ti + 2 To SSD',
|
||
'Configuration complète',
|
||
'Installation sur site',
|
||
'Formation équipe (2–3 h)',
|
||
'1<sup>re</sup> année logiciel + support'
|
||
] %}
|
||
<li class="flex items-start gap-2 text-white/80">
|
||
<span class="text-brand-b3 mt-0.5" aria-hidden="true">{{ icon_check | safe }}</span>
|
||
<span>{{ inc | safe }}</span>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</div>
|
||
|
||
<p class="text-xs text-white/60 text-center mt-6 max-w-2xl mx-auto">
|
||
DictIA LOCAL est admissible à l'achat direct gouvernemental sans appel d'offres (seuil 34 700 $ — Règlement sur les contrats d'approvisionnement, art. 15). Pour > 660 h audio/mois ou SLA 99,9 %, demandez une <a href="/contact?pro-plus=1" class="grad-text underline">soumission Pro+</a>.
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
{# ===== EXPORT FORMATS DEEP-DIVE (test compat) ===== #}
|
||
<section class="bg-white py-16" aria-labelledby="exports-title">
|
||
<div class="max-w-[1200px] mx-auto px-6">
|
||
<div class="text-center max-w-2xl mx-auto mb-10">
|
||
<p class="eyebrow grad-text mb-3">FORMATS D'EXPORT</p>
|
||
<h2 id="exports-title" class="text-[clamp(1.75rem,2.5vw,2.25rem)] font-black text-brand-navy">
|
||
7 formats, prêts pour vos workflows.
|
||
</h2>
|
||
</div>
|
||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-7 gap-3">
|
||
{% for fmt in [
|
||
{'ext': 'DOCX', 'use': 'Word avec timestamps cliquables'},
|
||
{'ext': 'PDF', 'use': 'Procès-verbaux signés'},
|
||
{'ext': 'SRT', 'use': 'Sous-titres vidéo'},
|
||
{'ext': 'VTT', 'use': 'Web/streaming standard'},
|
||
{'ext': 'TXT', 'use': 'Texte brut universel'},
|
||
{'ext': 'JSON', 'use': 'Pipeline développeur'},
|
||
{'ext': 'MD', 'use': 'Notion, Obsidian, GitHub'}
|
||
] %}
|
||
<div class="ani-lift bg-brand-bg p-4 rounded border border-brand-border text-center">
|
||
<p class="text-base font-black font-mono text-brand-navy">{{ fmt.ext }}</p>
|
||
<p class="text-xs text-brand-navy/70 mt-1">{{ fmt.use | safe }}</p>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{# ===== TECH SPECS (préservé pour rétro-compat tests) ===== #}
|
||
<section class="bg-brand-bg py-16" aria-labelledby="specs-title">
|
||
<div class="max-w-[1060px] mx-auto px-6">
|
||
<div class="text-center max-w-2xl mx-auto mb-10">
|
||
<p class="eyebrow grad-text mb-3">SOUS LE CAPOT</p>
|
||
<h2 id="specs-title" class="text-[clamp(1.75rem,2.5vw,2.25rem)] font-black text-brand-navy">
|
||
Spécifications techniques détaillées.
|
||
</h2>
|
||
</div>
|
||
<div class="grid md:grid-cols-2 gap-4">
|
||
{% for spec in [
|
||
{'title': 'Modèle ASR', 'desc': 'WhisperX Large-v3 (1,55 G paramètres) fine-tuné sur audio professionnel québécois. Format ONNX optimisé GPU.'},
|
||
{'title': 'Diarisation', 'desc': 'pyannote 3.x — clustering hiérarchique sur embeddings ECAPA-TDNN. 1 à 8 locuteurs détectés automatiquement.'},
|
||
{'title': 'LLM (résumés / Q&R)', 'desc': 'Mistral 7B Instruct quantifié 4-bit. Inférence locale sur le même GPU. Aucune sortie réseau.'},
|
||
{'title': 'Stack web', 'desc': 'Flask 2.3 (backend) + Vue 3 / Alpine.js (front). Chiffrement AES-256 au repos. AGPL v3.'},
|
||
{'title': 'Audio supportés (WAV, MP3, M4A, FLAC, OGG, WebM)', 'desc': 'Jusqu\'à 8 h par fichier. Conversion ffmpeg automatique. Compatible enregistrements Zoom et Teams.'},
|
||
{'title': 'Langues', 'desc': 'Optimisé français québécois. Aussi : français de France, anglais (canadien et US), espagnol, allemand, mandarin, russe.'}
|
||
] %}
|
||
<article class="ani-fade ani-lift bg-white p-5 rounded border border-brand-border" style="--delay: {{ loop.index0 * 50 }}ms;" data-ani-fade>
|
||
<h3 class="text-base font-bold mb-2 text-brand-navy">{{ spec.title | safe }}</h3>
|
||
<p class="text-sm text-brand-navy/70 leading-relaxed">{{ spec.desc | safe }}</p>
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{# ===== CONFORMITÉ — RÉSUMÉ ===== #}
|
||
<section id="conformite-resume" class="bg-white py-20 scroll-mt-32" aria-labelledby="conformite-resume-title">
|
||
<div class="max-w-[1060px] mx-auto px-6">
|
||
<div class="text-center max-w-3xl mx-auto mb-10">
|
||
<p class="eyebrow grad-text mb-4">CONFORMITÉ</p>
|
||
<h2 id="conformite-resume-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black text-brand-navy">
|
||
<span class="ani-underline" data-ani-underline>Conforme par construction.</span>
|
||
</h2>
|
||
<p class="text-lg text-brand-navy/70 mt-6">
|
||
Architecture pensée d'abord pour la Loi 25 et les ordres professionnels québécois — pas une certification ajoutée après coup.
|
||
</p>
|
||
</div>
|
||
|
||
<ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-10" role="list">
|
||
{% for chip in [
|
||
{'title': 'Loi 25', 'desc': 'La voix est biométrique — le traitement local élimine le risque.'},
|
||
{'title': 'Loi 96', 'desc': 'Interface, documentation et support 100 % français.'},
|
||
{'title': '9 ordres pros', 'desc': 'Mappé Barreau, CNQ, CPA, ChAD, OACIQ, CMQ, OIIQ, OPQ, OEQ.'},
|
||
{'title': '0 Cloud Act US', 'desc': 'Aucun fournisseur soumis à la juridiction américaine.'}
|
||
] %}
|
||
<li class="ani-fade ani-lift bg-brand-bg p-5 rounded border border-brand-border" style="--delay: {{ loop.index0 * 60 }}ms;" data-ani-fade>
|
||
<span class="inline-flex items-center gap-1.5 bg-brand-navy text-white text-xs font-black uppercase tracking-wider px-2.5 py-1 rounded-full mb-3">
|
||
<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"><path d="M5 13l4 4L19 7"/></svg>
|
||
{{ chip.title | safe }}
|
||
</span>
|
||
<p class="text-sm text-brand-navy/80 leading-relaxed">{{ chip.desc | safe }}</p>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
|
||
<div class="text-center">
|
||
<a href="/conformite" class="inline-flex items-center gap-2 text-base font-semibold grad-text hover:underline focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2">
|
||
Voir le détail conformité
|
||
{{ icon_link | safe }}
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{# ===== INTEGRATIONS GRID (compat tests + plugin partner list) ===== #}
|
||
<section class="bg-brand-bg py-12" aria-labelledby="integrations-grid-title">
|
||
<div class="max-w-[1200px] mx-auto px-6">
|
||
<h2 id="integrations-grid-title" class="sr-only">Liste détaillée des intégrations partenaires</h2>
|
||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3" role="list">
|
||
{% for integ in [
|
||
{'name': 'Microsoft Word', 'desc': 'Plugin natif (.docx)'},
|
||
{'name': 'Microsoft Outlook','desc': 'Pièces jointes audio'},
|
||
{'name': 'Microsoft Teams', 'desc': 'Enregistrements de réunions'},
|
||
{'name': 'Notion', 'desc': 'Pages markdown auto'},
|
||
{'name': 'Obsidian', 'desc': 'Notes timestamped'},
|
||
{'name': 'Zapier', 'desc': 'Workflows no-code'},
|
||
{'name': 'Make (Integromat)','desc': 'Scénarios visuels'},
|
||
{'name': 'n8n', 'desc': 'Open source self-host'}
|
||
] %}
|
||
<div class="bg-white p-4 rounded border border-brand-border text-center" role="listitem">
|
||
<p class="text-sm font-bold text-brand-navy font-mono">{{ integ.name | safe }}</p>
|
||
<p class="text-xs text-brand-navy/70 mt-1">{{ integ.desc | safe }}</p>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{# ===== CTA FINAL ===== #}
|
||
<section class="bg-white py-20 border-t border-brand-border" aria-labelledby="features-cta-title">
|
||
<div class="max-w-[820px] mx-auto px-6 text-center">
|
||
<p class="eyebrow grad-text mb-4">PRÊT À TRANSCRIRE EN LOCAL</p>
|
||
<h2 id="features-cta-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-5 text-brand-navy">
|
||
Prêt à transcrire <span class="grad-text">en local</span> ?
|
||
</h2>
|
||
<p class="text-lg text-brand-navy/70 mb-10">
|
||
Lancement printemps 2026 — pré-inscription ouverte. Conçu avec 9 ordres professionnels québécois.
|
||
</p>
|
||
<div class="flex flex-col sm:flex-row gap-3 justify-center">
|
||
{% from 'macros/button.html' import button %}
|
||
{{ button('Voir les tarifs', href='/tarifs', 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="M14 5l7 7m0 0l-7 7m7-7H3"/></svg>') }}
|
||
{{ button('Demander une démo', href='/contact', variant='secondary', size='lg') }}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{# ===== JS — IntersectionObserver pour ani-fade + ani-underline ===== #}
|
||
<script>
|
||
(function () {
|
||
if (typeof window === 'undefined' || typeof IntersectionObserver === 'undefined') return;
|
||
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||
var fadeEls = document.querySelectorAll('[data-ani-fade]');
|
||
var underlineEls = document.querySelectorAll('[data-ani-underline]');
|
||
if (reduced) {
|
||
fadeEls.forEach(function (el) { el.classList.add('is-visible'); });
|
||
underlineEls.forEach(function (el) { el.classList.add('is-visible'); });
|
||
return;
|
||
}
|
||
var io = new IntersectionObserver(function (entries) {
|
||
entries.forEach(function (e) {
|
||
if (!e.isIntersecting) return;
|
||
e.target.classList.add('is-visible');
|
||
io.unobserve(e.target);
|
||
});
|
||
}, { threshold: 0.15, rootMargin: '0px 0px -80px 0px' });
|
||
fadeEls.forEach(function (el) { io.observe(el); });
|
||
underlineEls.forEach(function (el) { io.observe(el); });
|
||
})();
|
||
</script>
|
||
|
||
{% endblock %}
|