V3 finalizes the radii pass to a fully brutalist/Swiss visual language: - Buttons (CTAs, submit, secondary, ghost, OAuth provider tiles): rounded-none (0px) - Form inputs (text/email/password/select/textarea/code-entry): rounded-none (0px) - Checkboxes: rounded-none (0px) — was rounded-sm - Small icon tiles (w-10 h-10 / w-12 h-12 grad-bg squares): rounded-none (0px) - Inline code blocks (totp recovery <pre>, secret <code>): rounded-none (0px) - Cards (pricing, bento, content panels, modals, prev/next nav): rounded (4px) — was rounded-lg - Alert / flash boxes: rounded (4px) — was rounded-lg - Pills, badges, status chips, ordres pros avatars, decorative cosmic orbs: rounded-full preserved - Legal _layout.html inline <style> blockquote/pre/code/draft-callout: border-radius 0 — was 4px Updated tests/test_marketing_landing_template.py assertions: - bento icon assertion: "grad-bg rounded " -> "grad-bg rounded-none " - pricing recommended frame: "rounded-lg" -> "rounded" (with strict trailing-char match to avoid rounded-none false positive) Verification: 18/18 legal tests pass, 58/58 marketing landing tests pass, 5/5 root redirect tests pass. Two pre-existing failures in test_marketing_secondary_pages (SOC 2 hedge text + gitea.innova-ai.ca URL) are unrelated to this radii pass.
380 lines
14 KiB
HTML
380 lines
14 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, #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 : {{ 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">
|
||
<span aria-hidden="true">←</span> 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">
|
||
Suivant <span aria-hidden="true">→</span>
|
||
</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>
|
||
<span aria-hidden="true">←</span>
|
||
<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 %}
|