Files
dictia-public/templates/legal/_layout.html
Allison 8d50d8ee01 refactor(ui): éliminer tous les emojis (SVG inline + texte propre, look pro/moderne)
Pass de modernisation visuelle : remplacement de TOUS les emojis Unicode dans
les templates marketing/legal/billing/auth par des SVG inline (style heroicons)
ou par du texte propre, pour un look SaaS pro à la Linear/Vercel/Stripe.

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 10:52:58 -04:00

382 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'marketing/base.html' %}
{% block title %}{{ title }} — DictIA{% endblock %}
{% block description %}{{ description }}{% endblock %}
{% block head_extra %}
<style>
/* ---------------------------------------------------------------------------
Typographie pour le markdown rendu (héritée de B-2.9, étendue B-2.10).
Inlinée pour ne pas avoir à reconstruire static/css/marketing.css.
--------------------------------------------------------------------------- */
.legal-content h2 {
position: relative;
font-size: 1.5rem; /* 24px */
line-height: 2rem;
font-weight: 700;
color: #060d1a; /* brand-navy */
margin-top: 2.75rem;
margin-bottom: 1rem;
letter-spacing: -0.022em;
scroll-margin-top: 90px; /* pour ancres sous le header sticky */
}
.legal-content h2::after {
content: '';
display: block;
width: 56px;
height: 4px;
margin-top: 0.5rem;
border-radius: 4px;
background: linear-gradient(118deg, #0062ff, #00bdd8 52%, #00c896);
}
.legal-content h3 {
font-size: 1.25rem; /* 20px */
line-height: 1.75rem;
font-weight: 600;
color: #060d1a;
margin-top: 2rem;
margin-bottom: 0.75rem;
scroll-margin-top: 90px;
}
.legal-content h4 {
font-size: 1.05rem;
font-weight: 600;
color: #060d1a;
margin-top: 1.25rem;
margin-bottom: 0.5rem;
}
.legal-content p {
margin-bottom: 1rem;
font-size: 1rem;
line-height: 1.75;
}
.legal-content ul,
.legal-content ol {
margin-bottom: 1rem;
margin-left: 1.5rem;
line-height: 1.75;
}
.legal-content ul { list-style-type: disc; list-style-position: outside; }
.legal-content ol { list-style-type: decimal; list-style-position: outside; }
.legal-content li { margin-bottom: 0.35rem; }
.legal-content a {
background: linear-gradient(118deg, #0062ff, #00bdd8 52%, #00c896);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 600;
text-decoration: underline;
text-decoration-color: #0062ff;
}
.legal-content a:focus-visible {
outline: 2px solid #0062ff;
outline-offset: 2px;
border-radius: 2px;
}
.legal-content table {
width: 100%;
margin: 1rem 0 1.5rem;
border-collapse: collapse;
font-size: 0.875rem;
}
.legal-content th,
.legal-content td {
border: 1px solid #e6ebf2;
padding: 0.6rem 0.75rem;
text-align: left;
vertical-align: top;
}
.legal-content th {
background-color: #f7f9fc;
font-weight: 600;
color: #060d1a;
}
.legal-content tbody tr:nth-child(even) td {
background-color: #fafbfd;
}
.legal-content blockquote {
border-left: 4px solid #0062ff;
background-color: rgba(247, 249, 252, 0.6);
padding: 0.75rem 1rem;
margin: 1.25rem 0;
border-radius: 0;
font-style: italic;
color: rgba(6, 13, 26, 0.75);
}
.legal-content code {
padding: 0.15rem 0.4rem;
background-color: #f7f9fc;
border-radius: 0;
font-size: 0.875rem;
font-family: 'JetBrains Mono Variable', 'JetBrains Mono', monospace;
}
.legal-content pre {
background-color: #f7f9fc;
border: 1px solid #e6ebf2;
border-radius: 0;
padding: 1rem;
overflow-x: auto;
margin-bottom: 1.25rem;
font-size: 0.875rem;
}
.legal-content pre code {
background-color: transparent;
padding: 0;
}
.legal-content hr {
margin: 2rem 0;
border: none;
border-top: 1px solid #e6ebf2;
}
.legal-content strong { font-weight: 700; color: #060d1a; }
/* DRAFT callout — visually distinct yellow banner */
.legal-content .draft-callout,
.legal-draft-callout {
background-color: #fffbeb;
border-left: 4px solid #f59e0b;
padding: 0.75rem 1rem;
margin: 1rem 0 1.5rem;
border-radius: 0;
font-size: 0.9rem;
color: #78350f;
}
/* ---------------------------------------------------------------------------
Sticky TOC + breadcrumb (desktop ≥ lg).
--------------------------------------------------------------------------- */
.legal-toc {
position: sticky;
top: 86px; /* sous header 62px + marge */
max-height: calc(100vh - 110px);
overflow-y: auto;
}
.legal-toc a {
border-left: 2px solid transparent;
transition: color 150ms ease, border-color 150ms ease, background-color 150ms ease;
}
.legal-toc a:hover {
background-color: rgba(0, 98, 255, 0.05);
}
.legal-toc a.is-active {
border-left-color: #0062ff;
color: #0062ff !important;
background-color: rgba(0, 98, 255, 0.06);
}
.legal-breadcrumb {
position: sticky;
top: 62px;
z-index: 30;
background-color: rgba(247, 249, 252, 0.92);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border-bottom: 1px solid #e6ebf2;
}
/* ---------------------------------------------------------------------------
Print stylesheet — hide nav chrome, keep article + header.
--------------------------------------------------------------------------- */
@media print {
header.fixed,
.legal-breadcrumb,
.legal-toc-wrapper,
.legal-prev-next,
footer {
display: none !important;
}
main { padding-top: 0 !important; }
.legal-content a {
color: #000 !important;
background: none !important;
-webkit-text-fill-color: #000 !important;
text-decoration: underline !important;
}
body { background: white !important; }
}
</style>
{% endblock %}
{% block content %}
{# Skip link (WCAG 2.4.1) — visible uniquement au focus clavier. #}
<a href="#main-content"
class="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[100] focus:px-4 focus:py-2 focus:bg-brand-navy focus:text-white focus:rounded-none focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2">
Aller au contenu principal
</a>
{# Breadcrumb sticky #}
<nav class="legal-breadcrumb px-4 py-3" aria-label="Fil d'Ariane">
<ol class="max-w-[1200px] mx-auto flex flex-wrap items-center gap-2 text-xs md:text-sm text-brand-navy/70">
<li><a href="/" class="hover:text-brand-b1 focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2 rounded-none">Accueil</a></li>
<li aria-hidden="true" class="text-brand-navy/40"></li>
<li><a href="{{ url_for('legal.legal_index') }}" class="hover:text-brand-b1 focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2 rounded-none">Documents légaux</a></li>
<li aria-hidden="true" class="text-brand-navy/40"></li>
<li class="text-brand-navy font-semibold truncate" aria-current="page">{{ title }}</li>
</ol>
</nav>
<section class="bg-brand-bg pt-8 pb-16 px-4">
<div class="max-w-[1200px] mx-auto lg:grid lg:grid-cols-[1fr_240px] lg:gap-10">
{# Article principal #}
<article id="main-content"
role="main"
aria-labelledby="legal-title"
class="bg-white p-6 md:p-10 rounded border border-brand-border shadow-cta order-1">
<header class="mb-8 pb-6 border-b border-brand-border">
<p class="text-xs uppercase tracking-wider text-brand-navy/70 mb-2">Document légal DictIA</p>
<h1 id="legal-title" class="text-3xl md:text-4xl font-black text-brand-navy mb-4 tracking-tight">{{ title }}</h1>
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-brand-navy/70">
<span class="inline-flex items-center gap-1.5">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="3" y="4" width="18" height="18" rx="2" ry="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>Version <strong class="text-brand-navy">{{ legal_version }}</strong></span>
</span>
<span class="text-brand-navy/40" aria-hidden="true">·</span>
<span>Dernière mise à jour&nbsp;: {{ legal_version }}</span>
<span class="text-brand-navy/40" aria-hidden="true">·</span>
<span>RPRP&nbsp;: <a href="mailto:rprp@dictia.ca" class="grad-text font-semibold underline">rprp@dictia.ca</a></span>
</div>
</header>
{# TOC mobile (collapsible) — visible < lg seulement #}
<details class="lg:hidden mb-6 border border-brand-border rounded bg-brand-bg/50">
<summary class="cursor-pointer px-4 py-3 text-sm font-semibold text-brand-navy flex items-center justify-between focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2 rounded">
<span>Sur cette page</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<polyline points="6 9 12 15 18 9"/>
</svg>
</summary>
<ul id="legal-toc-mobile" class="px-4 pb-3 pt-1 space-y-1 text-sm">
{# Rempli côté JS (Alpine via init du desktop). #}
</ul>
</details>
<div class="legal-content text-brand-navy/90 leading-relaxed">
{{ content | safe }}
</div>
{# Prev / Next navigation #}
{% if prev_page or next_page %}
<nav class="legal-prev-next mt-12 pt-6 border-t border-brand-border grid sm:grid-cols-2 gap-3"
aria-label="Navigation entre documents légaux">
{% if prev_page %}
<a href="{{ url_for('legal.legal_page', page=prev_page) }}"
rel="prev"
class="block p-4 bg-brand-bg/60 border border-brand-border rounded hover:border-brand-b1 hover:bg-white transition focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2">
<span class="block text-xs uppercase tracking-wider text-brand-navy/60 mb-1 inline-flex items-center gap-1.5">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-3.5 h-3.5" aria-hidden="true"><path d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
Précédent
</span>
<span class="block text-sm font-semibold text-brand-navy">{{ prev_title }}</span>
</a>
{% else %}
<span></span>
{% endif %}
{% if next_page %}
<a href="{{ url_for('legal.legal_page', page=next_page) }}"
rel="next"
class="block p-4 bg-brand-bg/60 border border-brand-border rounded hover:border-brand-b1 hover:bg-white transition focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2 sm:text-right">
<span class="block text-xs uppercase tracking-wider text-brand-navy/60 mb-1 inline-flex items-center gap-1.5 sm:justify-end">
Suivant
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-3.5 h-3.5" aria-hidden="true"><path d="M14 5l7 7m0 0l-7 7m7-7H3"/></svg>
</span>
<span class="block text-sm font-semibold text-brand-navy">{{ next_title }}</span>
</a>
{% endif %}
</nav>
{% endif %}
<footer class="mt-8 pt-6 border-t border-brand-border text-sm text-brand-navy/70">
<p class="inline-flex items-center gap-1.5">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" aria-hidden="true"><path d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
<a href="{{ url_for('legal.legal_index') }}" class="grad-text font-semibold">Index des documents légaux</a>
</p>
</footer>
</article>
{# TOC desktop — sidebar sticky #}
<aside class="legal-toc-wrapper hidden lg:block order-2"
aria-label="Table des matières">
<div x-data="legalToc()"
x-init="init()"
class="legal-toc bg-white border border-brand-border rounded p-5 mt-0">
<h2 class="text-xs font-bold uppercase tracking-wider text-brand-navy/70 mb-3">
Sur cette page
</h2>
<ul role="list">
<template x-for="item in items" :key="item.id">
<li>
<a :href="'#' + item.id"
:class="active === item.id ? 'is-active font-semibold' : ''"
:aria-current="active === item.id ? 'true' : null"
class="block py-1.5 pl-3 pr-2 text-sm text-brand-navy/70 hover:text-brand-b1 focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2 rounded-none"
x-text="item.text"></a>
</li>
</template>
<template x-if="items.length === 0">
<li class="text-xs text-brand-navy/50 italic py-1">
Aucune section à afficher.
</li>
</template>
</ul>
</div>
</aside>
</div>
</section>
{% endblock %}
{% block scripts %}
<script>
// Construit la TOC en scannant les <h2> du contenu rendu, met l'élément actif
// à jour via IntersectionObserver. Synchronise aussi la liste mobile.
function legalToc() {
return {
items: [],
active: '',
init() {
const populate = () => {
const headings = Array.from(document.querySelectorAll('.legal-content h2'));
this.items = headings
.filter(h => h.id) // markdown.toc auto-id; skip if missing
.map(h => ({ id: h.id, text: h.textContent.trim() }));
// Mirror dans le <details> mobile.
const mobileList = document.getElementById('legal-toc-mobile');
if (mobileList) {
mobileList.innerHTML = '';
this.items.forEach(it => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = '#' + it.id;
a.textContent = it.text;
a.className = 'block py-1.5 text-brand-navy/80 hover:text-brand-b1 focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2 rounded-none';
li.appendChild(a);
mobileList.appendChild(li);
});
}
if (headings.length === 0) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) this.active = e.target.id;
});
}, { rootMargin: '-100px 0px -60% 0px' });
headings.forEach(el => observer.observe(el));
};
// Lance après que Alpine ait rendu et que le DOM soit posé.
if (document.readyState === 'complete') populate();
else window.addEventListener('load', populate, { once: true });
},
};
}
</script>
{% endblock %}