Files
dictia-public/templates/legal/_layout.html
Allison 575db5e342 feat(brand): logo officiel DictIA + palette blue/cyan/fuchsia (matche le logo)
Logos officiels installés :
- static/images/dictia-logo.png (28 KB optimisé 256×256)
- static/images/dictia-logo-128.png (10 KB retina)
- static/images/dictia-logo-fullres.png (originaux conservés OG/social)
- static/images/dictia-logo.svg + dictia-logo-nom.svg (cleaned C2PA metadata)
- Header marketing/base.html : <img> 40×40 + wordmark "DictIA" + tagline "Transcription"
- Footer marketing/_footer.html : <img> 36×36 + wordmark
- Favicon mis à jour vers logo PNG

Note : SVG sources sont des PNG base64 wrappés (pas de vrais paths) — PNG utilisé
en production (8× plus léger), SVG conservé pour fallback.

Palette canonique alignée sur le logo :
- brand-b1 : #7c3aed (mauve) → #2563eb (blue-600 vibrant — primary)
- brand-b2 : #a855f7 (mauve clair) → #06b6d4 (cyan-500 — aqua mid)
- brand-b3 : #06b6d4 (aqua) → #c026d3 (fuchsia-600 — magenta accent)
- Gradient signature : linear-gradient(118deg, #2563eb, #06b6d4 52%, #c026d3)
- Box shadow CTA : rgba(37,99,235,0.28/0.42)
- 72 remplacements hex/rgba dans 5 templates marketing/legal + email service

Tests : 81 passed / 3 failed (3 échecs pré-existants /blog + trust-bar phrasing,
non liés à ce changement). 0 régression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 15:54:17 -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, #2563eb, #06b6d4 52%, #06b6d4);
}
.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, #2563eb, #06b6d4 52%, #06b6d4);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 600;
text-decoration: underline;
text-decoration-color: #2563eb;
}
.legal-content a:focus-visible {
outline: 2px solid #2563eb;
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 #2563eb;
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(37,99,235, 0.05);
}
.legal-toc a.is-active {
border-left-color: #2563eb;
color: #2563eb !important;
background-color: rgba(37,99,235, 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 %}