Décale la palette canonique DictIA du bleu/cyan/vert vers mauve/violet/aqua
afin de distinguer visuellement le produit DictIA des autres outils InnovA AI
(qui restent sur palette bleue) tout en gardant la même structure de gradient,
mêmes tokens Tailwind, et même intensité visuelle.
Mapping appliqué:
- Hex semantic: #0062ff → #7c3aed (mauve), #00bdd8 → #a855f7 (lighter mauve)
- Hex aqua décoratif: #00c896 → #06b6d4 (cyan-500, aqua préservé)
- Hex secondaire: #6B9FFF / rgba(107,159,255,*) → #a78bfa (violet-400)
- Hex blue-700 #1d4ed8 (cadre reg label) → #7c3aed (mauve)
- Box shadows / rgba opacités: rgba(0,98,255,*) → rgba(124,58,237,*)
- Décoratif (orbes cosmiques, glows): mauve+aqua mix pour préserver l'effet
"cosmic dégradé" — orbe primaire en mauve, orbes secondaires en aqua
- Hub network DictIA: Documents=mauve, Communication=aqua, Automatisation
reste #8b5cf6 (déjà violet, marqueur visuel distinct via positionnement)
- Couleurs sémantiques fonctionnelles (red/green pour erreurs/succès, amber
pour alertes) inchangées
Fichiers modifiés:
- static/css/tailwind.config.js (brand.b1/b2/b3 + brand-grad + boxShadow.cta
+ boxShadow.cta-hover + keyframes.tc-pulse-glow)
- static/css/marketing.css (rebuild Tailwind: 169356 → 163036 bytes)
- templates/legal/{_layout,index}.html
- templates/marketing/{landing,fonctionnalites,conformite,tarifs}.html
- tests/test_marketing_landing_template.py (test_hero_has_cosmic_orbs_background
mis à jour avec assertions mauve/aqua au lieu de blue/cyan/green)
Hors scope (non touchés):
- Couleurs Tailwind utility (red/green/amber/emerald) sémantiques
- --brand-navy* (backgrounds dark restent neutres)
- Templates legacy (account.html, admin.html, components/, modals/)
- #8b5cf6 (Automatisation hub), #f59e0b (alertes), #ef4444 (erreurs)
Tests: 111 passed, 5 failed (toutes 5 pré-existantes, non liées aux couleurs:
/blog link manquant, MAPP eyebrow, SOC 2 phrasing, Gitea URL).
HTTP 200 vérifié sur /, /fonctionnalites, /tarifs, /conformite, /legal/,
/legal/conditions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
382 lines
15 KiB
HTML
382 lines
15 KiB
HTML
{% 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, #7c3aed, #a855f7 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, #7c3aed, #a855f7 52%, #06b6d4);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
color: transparent;
|
||
font-weight: 600;
|
||
text-decoration: underline;
|
||
text-decoration-color: #7c3aed;
|
||
}
|
||
.legal-content a:focus-visible {
|
||
outline: 2px solid #7c3aed;
|
||
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 #7c3aed;
|
||
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(124, 58, 237, 0.05);
|
||
}
|
||
.legal-toc a.is-active {
|
||
border-left-color: #7c3aed;
|
||
color: #7c3aed !important;
|
||
background-color: rgba(124, 58, 237, 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 : {{ legal_version }}</span>
|
||
<span class="text-brand-navy/40" aria-hidden="true">·</span>
|
||
<span>RPRP : <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 %}
|